TypeScript делает параметр необязательным, если он не предоставлен

#javascript #typescript #types

#javascript #typescript #типы

Вопрос:

У меня есть этот интерфейс:

 export interface IScene<R extends string> {
  path: R;
  params?: SceneParams;
}
  

Интерфейс SceneParams:

 export interface SceneParams {
  [key: string]: string;
}
  

Это работает абсолютно нормально, когда я создаю сцену, подобную:

 interface PostDetailScene extends IScene<PostRoute.Detail> {
  params: PostDetailSceneParams;
}
  

Параметры PostDetailSceneParams:

 export interface PostDetailSceneParams extends SceneParams {
  postId: string;
}
  

Весь этот код получает правильную проверку типа:

 // CORRECT
getPathWithParams({
  path: PostRoute.Detail,
  params: { postId: '4' },
});

getPathWithParams({
  path: UserRoute.List,
});

getPathWithParams({
  path: UserRoute.List,
  params: undefined,
});

// ERROR
getPathWithParams({
  path: PostRoute.Detail,
  params: undefined,
});

getPathWithParams({
  path: PostRoute.Detail,
});

getPathWithParams({
  path: UserRoute.List,
  params: { wrongParam: 'value' },
});
  

Теперь у меня есть сцена, в которой я не хочу передавать какие-либо реквизиты. Эта сцена является UserListScene:

 interface UserListScene extends IScene<UserRoute.List> {
  params?: never;
}
  

Видите ли, я должен явно ввести params?: never (или params?: undefined — я также не знаю, какой тип мне следует использовать здесь, потому что здесь параметры действительно никогда не будут / не должны передаваться — но с never компилятор также удовлетворяется значением undefined, поэтому я не вижу такой большой разницы)

Мой вопрос таков: есть ли решение для изменения интерфейса IScene, чтобы мне не приходилось вводить, params?: never или params?: undefined когда для этой сцены нет параметров?

Я просто хочу написать:

 interface UserListScene extends IScene<UserRoute.List> {}
  

или:

 type UserListScene = IScene<UserRoute.List>;
  

Редактировать:

Эта функция также должна корректно проверять тип:

 export function getPathWithParams(scene: Scene): string {
  if (!scene.params) {
    return scene.path;
  }

  let pathWithParams: string = scene.path;
  const paramsAsString = Object.keys(scene.params);

  paramsAsString.forEach((param: string) => {
    pathWithParams = pathWithParams.replace(`:${param}`, scene.params[param]);
  });

  return pathWithParams;
}
  

Ответ №1:

Используйте два интерфейса (поскольку на самом деле это то, что у вас действительно есть здесь):

 export interface IScene<R extends string> {
  path: R;
}

export interface ISceneWithParams<R extends string> {
  path: R;
  params: SceneParams;
}
  

Комментарии:

1. Но тогда мне приходится выбирать между интерфейсами to каждый раз, когда я создаю сцену. Я просто хочу иметь один интерфейс для обоих случаев. Кроме того, я отредактировал свой ответ и добавил функцию, которая также должна корректно проверять тип.