@kot98

Как типизировать в ts?

Только начинаю разбираться в ts
const pages = {
  first: 'Первая',
  second: 'Вторая',
  ...
}

const getPageDesc = computed(() => {
  if (route.name !== null && route.name !== undefined) {
    return pages[route.name]
  }
})

В итоге получаю ошибку типов. Подскажите в чем проблема.
  • Вопрос задан
  • 198 просмотров
Решения вопроса 1
Aetae
@Aetae Куратор тега TypeScript
Тлен
Вопрос конечно в духе "я его того, а он мне нихрна, чаво это он". Где конкретика блин? Что именно за ошибка? Любая ошибка TS - это "ошибка типов", он для этого и существует. Что у тебя в route?

Но окей, предположим, что route у тебя - это текущий роут vue-рутера, тогда ошибка у тебя скорее всего выглядит как-то так:
TS7053: Element implicitly has an 'any' type because expression of type 'RouteRecordName' can't be used to index type '{ first: string; second: string; }'.   No index signature with a parameter of type 'string' was found on type '{ first: string; second: string; }'.

Собственно прочитав текст ошибки можно уже догадаться, в чём проблема: у (неявно выведенного за отсутствием явного объявления) типа объекта pages есть ключи типа 'first' | 'second' | ..., но нет index signature типа string, т.е. не указано, что ключом может быть любая строка, а не только конкретные'first' | 'second' | ....
route.name же после проверки на пустоту имеет тип string | symbol. Ты не можешь у объекта с чётко ограниченным набором ключей брать значение по произвольному строковому/символьному.

Прямое решение в лоб:
Задать тип pages позволяющий рандомные ключи, например
const pages: Record<PropertyKey, string> = {
  first: 'Первая',
  second: 'Вторая',
  ...
}

Всё сразу заработает, но это не спасёт тебя от ошибок(например опечаток).


Не менее прямой вариант
(но с другой стороны):
Кастануть нужный тип руками: ... = pages[route.name as keyof typeof pages]
Ведь мы уверены, что name в route всегда будет одним из ключей pages. Уверены же?..

Энтерпрайз решение(ничем не лучше предыдущих, зато выглядит солидно):
Твой файл routes.ts:
export enum ERoutes {
  FIRST = 'first',
  SECOND = 'second'
}

const routes = [
  {
     name: ERoutes.FIRST,
     ...
  },
  {
     name: ERoutes.SECOND,
     ...
  },
  ...
]

В коде:
const pages: Record<ERoutes, string> = {
  [ERoutes.FIRST]: 'Первая',
  [ERoutes.SECOND]: 'Вторая',
  ...
}

... = pages[route.name as ERoutes]


Надмозговое решение("как батька"):
Твой файл routes.ts:
const routes = [
  {
     name: 'first',
     ...
  },
  {
     name: 'second',
     ...
  },
  ...
] as const satisfies ReadonlyArray<ReadonlyRouteRecordRaw>;

type ReadonlyRouteRecordRaw = Omit<RouteRecordRaw, 'children'> & {
  children?: ReadonlyArray<ReadonlyRouteRecordRaw>;
};

type ExtractNames<Route> = Route extends { name: infer Name } ? Name : never;
type FlattenChildren<Route> = Route extends { children: ReadonlyArray<infer Children> }
  ? FlattenChildren<Children> | Route
  : Route;

// с помощью магии ts вытаскиваем в тип RouteNames все заданные у нас имена маршрутов
export type RouteNames = ExtractNames<FlattenChildren<typeof routes[number]>>;

// с помощью магии же прокидывем их прямо в декларацию vue-router
declare module 'vue-router' {
  export interface RouteLocationNormalizedLoaded {
    name: RouteNames | null | undefined;
  }
}

satisfies
satisfies - новая фича ts 4.9, в предыдущих версиях того же можно добиться сделав обёртку вида:
const narrowRoutesTypeWrapper = <T extends ReadonlyArray<ReadonlyRouteRecordRaw>>(routes: T) => routes;
const routes = narrowRoutesTypeWrapper([ ... ] as const);

И твой код заработает вообще без изменений (если в pages есть все нужные ключи).)
Однако для удобства можно написать так:
const pages: Record<RouteNames, string> = {
  first: 'Первая',
  second: 'Вторая',
  ...
}
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
DarkCoder30
@DarkCoder30
Tech Lead
Проблема в том, что TypeScript не знает тип route.name
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы