Typescript: могу ли я динамически генерировать типы внутри самого TS? - PullRequest
0 голосов
/ 17 апреля 2019

Я хочу, чтобы типы моих редукционных действий были статически типизированы, поэтому я могу извлечь выгоду из обнаружения опечаток, автозаполнения и т. Д. В коде, который имеет дело с такими действиями, как редукторы и т. Д.

Чтобы это работало хорошо, мне действительно нужно статически набирать структуру каждого действия в моем приложении, вот так

type Actions =
  | { type: 'A_PENDING' }
  | { type: 'A_FULFILLED', payload: { a: number } }
  | { type: 'A_REJECTED', error: Error }
  | { type: 'B_PENDING' }
  | { type: 'B_FULFILLED', payload: { b: number } }
  | { type: 'B_REJECTED', error: Error }
  | { type: 'C_PENDING' }
  | { type: 'C_FULFILLED', payload: { c: number } }
  | { type: 'C_REJECTED', error: Error }

Тем не менее, есть много повторений в написании этих типов для всех моих действий.

Я знаю, что могу писать шаблоны редакторов, чтобы буквально генерировать этот код для меня, но мне было интересно, есть ли какой-нибудь "нативный" способ TS для генерации таких шаблонов определений типов.

Я бы вообразил синтаксис примерно так (псевдокод)

typegenerator AsyncAction = (BaseString, PayloadType) => 
  | { type: BaseString + '_PENDING' }
  | { type: BaseString + '_FULFILLED' }, payload: PayloadType }
  | { type: BaseString + '_REJECTED' }, error: Error }

type Actions =
  | AsyncAction('A', { a: number })
  | AsyncAction('B', { b: number })
  | AsyncAction('C', { c: number })

Существует ли что-нибудь подобное, или мне лучше просто делать генерацию буквального кода?

Ответы [ 2 ]

1 голос
/ 17 апреля 2019

Ну, в настоящее время невозможно объединить строки на уровне типа. (аналогичный запрос для функции здесь )

Но следующее может сделать код менее повторяющимся (если не менее повторяющимся, то, по крайней мере, более систематически повторяющимся)

type AsyncActionType = "A" | "B";
type AsyncActionPayloads = {
    "A": { a: number },
    "B": { b: string }
}

type PendingActionTypes = {
    "A": "A_PENDING",
    "B": "B_PENDING"
}

type FulfilledActionTypes = {
    "A": "A_FULFILLED",
    "B": "B_FULFILLED"
}

type RejectedActionTypes = {
    "A": "A_REJECTED",
    "B": "B_REJECTED"
}

type AsyncAction = {
    [T in AsyncActionType]: {
        type: PendingActionTypes[T]
    } | {
        type: FulfilledActionTypes[T],
        payload: AsyncActionPayloads[T]
    } | {
        type: RejectedActionTypes[T],
        error: Error
    }
}[AsyncActionType];

type Action =
    { type: "MY_OTHER_NON_ASYNC_ACTION" } |
    { type: "MY_ANOTHER_NON_ASYNC_ACTION", payload: { foo: number } } |
    AsyncAction;

Демонстрация игровой площадки . Наведите курсор мыши на AsyncAction, и вы увидите, что это объединение всех асинхронных действий, которые вы ожидаете. Также попробуйте удалить "B": "B_PENDING" из PendingActionTypes, вы получите ошибку компиляции.

Также вы видите, что надоедливые части PendingActionTypes, FulfilledActionTypes и RejectedActionTypes можно переместить в другой файл, который можно сгенерировать с помощью сценария nodejs, который читает файл, содержащий AsyncActionType, и выполняет команды при изменении этого файла

0 голосов
/ 17 апреля 2019

Мне тоже хочется опубликовать этот ответ, потому что он ближе (на самом деле, довольно точный) к вашему псевдокоду. Я не публиковал это ранее, потому что это не уменьшает повторяемость, а просто вытесняет ее. (может быть, делает его более повторяющимся? ИДК). Если я игнорирую extends "A" | "B", который требует согласованности и делает его повторяющимся, мне больше нравится это решение.

type AsyncAction<T extends "A" | "B", P> = 
    | { type: AddSuffix<T, "PENDING"> }
    | { type: AddSuffix<T, "FULFILLED">, payload: P }
    | { type: AddSuffix<T, "REJECTED">, error: Error }

type Action =
    | AsyncAction<"A", { a: string }>
    | AsyncAction<"B", { b: string }>



type AddSuffix<
    T extends "A" | "B",
    S extends "PENDING" | "FULFILLED" | "REJECTED"
> =
    T extends "A" ?
        S extends "PENDING" ? "A_PENDING" :
        S extends "FULFILLED" ? "A_FULFILLED" :
        S extends "REJECTED" ? "A_REJECTED" :
        never :
    T extends "B" ?
        S extends "PENDING" ? "B_PENDING" :
        S extends "FULFILLED" ? "B_FULFILLED" :
        S extends "REJECTED" ? "B_REJECTED" :
        never : 
    never;

Демоверсия игровой площадки

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...