#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. Большое спасибо!! Я пытался заставить его работать с помощью экстракта, но это огромная помощь. Я наконец-то могу перестать тратить на это весь день…