Как ввести объект ключа: Функции редуктора в соответствии с интерфейсом в Typescript

#typescript #generics

Вопрос:

Рассмотрим следующий интерфейс:

 interface TestState {
  a: number;
  b: string;
}
 

Я пытаюсь написать универсальный тип, который указывает, что объект должен:

  • содержат те же ключи, что и предоставленный интерфейс (например, состояние теста).
  • для каждого ключа укажите значение функции редуктора с типом, указанным в TestState, в качестве типа самого состояния.

Так, например, объект, выполняющий это, будет выглядеть следующим образом:

 const test: StateSlices<TestState> = {
  a: (state: number, action: any) => state,
  b: (state: string, action: any) => state,
};
 

Я пытаюсь напечатать это так:

 type StateSlices<T, K extends keyof T> = Record<
  keyof T,
  (state: T[K], action: any) => T[K]
>;
 

Однако, если я затем попытаюсь написать это:

 const test: StateSlices<TestState, keyof TestState> = {
  a: (state: number, action: any) => state,
  b: (state: string, action: any) => state,
};
 

Я получаю ошибки компиляции, потому что Typescript не знает, собираюсь ли я использовать строки или числа:

 Type '(state: number, action: any) => number' is not assignable to type '(state: string | number, action: any) => string | number'.
  Types of parameters 'state' and 'state' are incompatible.
 

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

Любая помощь будет очень признательна.

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

В качестве дополнительного вопроса, если я пытаюсь связать свои различные фрагменты состояния с конкретными действиями, как это лучше всего сделать?

Например:

 const test: StateSlices<TestState, keyof TestState> = {
  a: (state: number, action: "ADD" | "SUBTRACT") => state,
  b: (state: string, action: "TO_LOWERCASE" | "TO_UPPERCASE") => state,
};
 

Каков был бы наилучший способ связать фрагмент » a » тестового состояния с соответствующими действиями, чтобы это привело к ошибке:

 const test: StateSlices<TestState, keyof TestState> = {
  a: (state: number, action: "TO_LOWERCASE" | "SUBTRACT") => state,
  b: (state: string, action: "SUBTRACT" | "TO_UPPERCASE") => state,
};
 

Еще раз спасибо вам.

Ответ №1:

Используйте сопоставленные типы:

 interface TestState {
  a: number;
  b: string;
}

type StateSlices<T> = {
    [K in keyof T]: (state: T[K], action: any) => T[K]
}

const test: StateSlices<TestState> = {
  a: (state, action) => state,
  b: (state, action) => state,
};
 

Игровая площадка

Приложение 1: Что касается второй части вашего вопроса, вы можете использовать условные типы для получения желаемых действий в зависимости от типа вашего состояния. Это сработает, но плохо масштабируется.

 interface TestState {
  a: number;
  b: string;
}

type GetAction<T> =
  T extends number
    ? "ADD" | "SUBTRACT"
    : T extends string
      ? "TO_LOWERCASE" | "TO_UPPERCASE"
      : never;

type StateSlices<T> = {
    [K in keyof T]: (state: T[K], action: GetAction<T[K]>) => T[K]
}

const test: StateSlices<TestState> = {
  a: (state, action) => state,
  b: (state, action) => state,
};
 

Игровая площадка

Приложение 2 Вы также можете использовать эту технику:

 interface TestState {
  a: number;
  b: string;
}

interface Actions {
  a: "ADD" | "SUBTRACT";
  b: "TO_LOWERCASE" | "TO_UPPERCASE";
}

type StateSlices<T> = {
    [K in keyof T]: (state: T[K], action: K extends keyof Actions ? Actions[K] : never) => T[K]
}

const test: StateSlices<TestState> = {
  a: (state, action) => state,
  b: (state, action) => state,
};
 

Плейгорунд

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

1. Спасибо! В качестве бонуса, как бы вы связали каждый фрагмент состояния с конкретными типами действий?

2. Пожалуйста, сначала уточните, какой тип каждого из них является желаемым action .

3. Я только что обновил вопрос, любая помощь была бы очень признательна, но очень благодарна за ваш ответ, который я отмечаю как правильный

4. Готово, надеюсь, это поможет!

5. Большое спасибо!! Я пытался заставить его работать с помощью экстракта, но это огромная помощь. Я наконец-то могу перестать тратить на это весь день…