Post #41
396
Второй подход к снаряду поиска типов
Сколько же решений по типизации экшенов выдает Stackoverflow, вплоть до пакетов с тремя звездочками на Github и каждый способ не лишен изъянов.
Возьмем например довольно очевидный способ создания экшена "в лоб":
И по общей договоренности с командой мы решили, что тип поля
И так, приступим исправлять типизацию.
Что же для нас такое этот
Это функция, принимающая
Чтобы это сделать мы воспользуемся таким вот хитрым хелпером, который позволит доказать, что тип действительно строка (если у вас есть идеи как это сделать проще - велком в личку).
Итоговый
P.S. В примерах не исключены ошибки ☺️
Всех с наступающим!🎄
#w_corner_ts
www.typescriptlang.org TS Playground - An online editor for exploring TypeScript and JavaScript The Playground lets you write TypeScript or JavaScript online in a safe and sharable way. Сколько же решений по типизации экшенов выдает Stackoverflow, вплоть до пакетов с тремя звездочками на Github и каждый способ не лишен изъянов.
Возьмем например довольно очевидный способ создания экшена "в лоб":
interface Action<T, P> {
readonly type: T;
readonly payload?: P;
}
const createAction = <T extends string, P>(type: T) => (payload: P): Action<T, P> => {
return { type, payload };
}
В таком случае от нас TS будет требовать пробрасывать вторым аргументом в дженерик payload, что нам не всегда нужно (тк нельзя в дженерик с двумя аргументами передавать только один, ну и если попробуем от этого избавиться, то все равно в конце концов мы теряем тип от type).И по общей договоренности с командой мы решили, что тип поля
type обязательно должен быть строкой.И так, приступим исправлять типизацию.
Что же для нас такое этот
createAction в итоге. Это функция, принимающая
type и payload и возвращающая объект с этими двумя полями, а главное сохраняющая типы.const createAction = (type) => (payload) => ({
type,
payload,
})
И первый тип, с которым надо нам надо разобраться - это тип Action, содержащий payload, type и более базовый тип - ReduxAction (для него достаточно поля type)interface ReduxAction<T extends string> {
type: T
}
export interface Action<Type extends string, Payload = void> extends ReduxAction<Type> {
payload?: Payload
}
Тут все довольно просто. Обернем в это createAction.const createAction = <T extends string, P>(type: T) => (payload?: P): Action<T, P> => ({
type,
payload,
})
Чтож, мы пришли примерно к тому же, что и было в примере с SO. Только payload вне зависимости от экшена не обязательный, а типы этого поля так и не выводятся. Добавим магии lookup types и посмотрим, что у нас получится.export const createAction = <A extends Action<A['type'], A['payload']>>(type: A['type']) => (payload: A['payload']) => ({
type,
payload,
})
Теперь у нас выводится типы тайпсрипта по типу экшена. Так мы не можем доказать тайпчекеру, что тип у A['type'] - строка, и это не сработает (пример). Впрочем, мы и не пытались.Чтобы это сделать мы воспользуемся таким вот хитрым хелпером, который позволит доказать, что тип действительно строка (если у вас есть идеи как это сделать проще - велком в личку).
type StingTypeField = {
type: string
}
type LookupStringTypeField<T, K> = K extends keyof T ? (T extends StingTypeField ? T[K] : never) : never
В LookupStringTypeField мы говорим, что поле в объекте точно есть поле K и оно точно строка, а иначе и не может быть (never).Итоговый
createAction:const createAction = <A extends Action<LookupStringTypeField<A, 'type'>, A['payload']>>(type: A['type']) => (payload: A['payload']) => ({
type,
payload,
})
И конечно ссылка с примером.P.S. В примерах не исключены ошибки ☺️
Всех с наступающим!🎄
#w_corner_ts
