TGViewer
Кузенков x IT-Хозяева Кузенков x IT-Хозяева @webcorner · 334 subscribers
Post #41 396
Второй подход к снаряду поиска типов

Сколько же решений по типизации экшенов выдает 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
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.
More from @webcorner
  1. Jun 29, 2026telega-gleam 2.0 🚀 В целом, я убил все остальные фреймворки для тг ботов. Расходимся. Гла…
  2. Mar 7, 2026Агенты в обучении Один из моих любимых примеров использования ИИ-агентов - самостоятельное…
  3. Feb 4, 2026https://t.me/harvardsplatebot Это был тест Harvard Bot 🤖 Кидаем фотку, анализируем еду 🌯
  4. Jan 26, 2026Вайбкод: практика 🛠 Пошаговая инструкция к вайбовой разработке. 📋 Продуктовый вижен Спер…
  5. Jan 26, 2026Вайбкод 🎸 Проекты для инди-дева — это всегда куча потраченного вне рабочего времени в вых…
  6. Nov 14, 2025Скоро будет почти 6 лет, как я занимаюсь индивидуальным и групповым менторством, помогаю р…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →