#typescript #generics #reducers
Вопрос:
Проблема
У меня есть два редуктора, которые используют одну и ту же логику для двух разных массивов, содержащих точки. Массив будет содержать только точки одного типа; этими типами являются A
и B
. Я хочу создать редуктор, который принимает массив точек типа A
или типа B
, а затем возвращает массив того же типа.
Когда я пытаюсь скомпилировать приведенный ниже код, я получаю эту ошибку:
Argument of type 'Partial<A> | Partial<B>' is not assignable to parameter of type 'Partial<T>'.
Type 'Partial<A>' is not assignable to type 'Partial<T>'.
Как я могу это исправить?
Проблемный Код
enum Category {
A,
B
}
interface A {
readonly category: Category.A;
}
interface B {
readonly category: Category.B;
}
Category =
const genericReducer = <T extends A | B>(
state: MyState,
action: Actions,
points: T[],
category: Category
): T[] => {
switch (action.type) {
case POINT_UPDATED: {
if (action.payload.category !== category) return points;
return updateItemInArray(points, action.payload.id, (stpt) => {
return updateObject(stpt, action.payload.newValues);
});
}
default:
return points;
}
};
ArrayUtils.updateObject
Для справки, вот updateObject
функция:
static updateObject = <T>(oldObject: T, newValues: Partial<T>) => {
// Encapsulate the idea of passing a new object as the first parameter
// to Object.assign to ensure we correctly copy data instead of mutating
return Object.assign({}, oldObject, newValues);
};
Функция updateItemInArray
static updateItemInArray = <T>(array: T[], itemId: string, updateItemCallback: (item: T) => T): T[] => {
const updatedItems = array.map((item) => {
if (item.id !== itemId) {
// Since we only want to update one item, preserve all others as they are now
return item;
}
// Use the provided callback to create an updated item
const updatedItem = updateItemCallback(item);
return updatedItem;
});
return updatedItems;
};
ПРАВКА №1
Вот ссылка CodeSandbox на мою последнюю попытку, основанную на ответе Линды Пейсте. В настоящее время все еще существует проблема с назначением типа Partial<T>
.
Комментарии:
1. Проблема, скорее всего, в вашей
updateItemInArray
функции.2. Пожалуйста, приведите воспроизводимый пример
3. Пожалуйста, укажите определение типа для
Actions
. Я почти уверен, что это причина проблемы, потомуaction.payload.newValues
что имеет типA | B
иupdateObject
ожидает, что типPartial<T>
гдеT extends A | B
. ЕслиT extends A | B
это не означает, чтоA | B
это может быть присвоено ему4. @капитан-йоссариан, пожалуйста, смотрите Правку №1 для ссылки на игровую площадку TS.
5. @капитан-йоссариан, я не сомневаюсь, что Линда сможет. Я довольно новичок в TS, и я поражен тем, как она заполнила пробелы. Я должен был просто предоставить пример CodeSandbox со всем кодом с самого начала.
Ответ №1:
Я начал пытаться заполнить недостающие части вашего кода, чтобы найти, в чем проблема. Сделав это, стало очевидно, что проблема в вашем Action
типе (как предположил @Alex Chashin).
Я знаю, что в этом Actions
есть type
то, что нужно включить POINT_UPDATED
. В нем также есть payload
. Полезная нагрузка включает в себя an id
, a category
и некоторые newValues
другие .
type Actions = {
type: typeof POINT_UPDATED;
payload: {
id: string;
category: Category;
newValues: ????;
}
}
Что такое newValues
? Основываясь на подписи updateObject
, мы знаем, что так и должно быть Partial<T>
. Но Actions
не знает, что T
это такое.
Я предполагаю , что ваш код использует что-то вроде newValues: A | B;
, что дает мне ошибку, которую вы опубликовали. Это недостаточно конкретно. Недостаточно знать, что наши новые ценности-это « A
или B
«. updateObject
говорит, что если T
есть A
, то newValues
должно быть A
, а если T
есть B
, то должны быть B
новые значения .
Поэтому вам Actions
нужно быть универсальным типом.
type Actions<T> = {
type: typeof POINT_UPDATED;
payload: {
id: string;
category: Category;
newValues: Partial<T>;
}
}
const genericReducer = <T extends A | B>(
state: MyState,
action: Actions<T>,
points: T[],
category: Category
): T[]
...
Я вижу ошибку на вашем updateItemInArray
при попытке доступа item.id
:
Свойство «id» не существует для типа «T»
Вам нужно уточнить тип T
такого, который он знает о свойстве id:
const updateItemInArray = <T extends {id: string}>(
Это приводит к новым ошибкам в вашем genericReducer
, потому что вы сказали, что T
это должно быть category
, но вы не сказали, что это должно быть id
. Мы можем исправить это с помощью:
const genericReducer = <T extends (A | B) amp; {id: string}>(
что то же самое, что
const genericReducer = <T extends {id: string; category: Category}>(
Хотя на самом деле кажется, что вы никогда category
не смотрите на свои точки T[]
зрения . Так что все, что вам действительно нужно, это:
const genericReducer = <T extends {id: string}>(
Ссылка на игровую площадку для машинописи
Редактировать:
Ошибка, которую вы получаете в своем пересмотренном коде, честно говоря, действительно глупая. Технически T
extends
тип с. {elevation: number}
Таким образом, есть вероятность, что для этого может потребоваться более конкретная версия типа, например {elevation: 99}
. В этом конкретном случае не было бы присвоения . {elevation: number}
Partial<{elevation: 99}>
По крайней мере, я думал, что в этом и была проблема. Однако мое первое исправление не сработало. Я попытался уточнить тип Actions
, чтобы сказать, что elevation
свойство должно соответствовать свойству from T
.
type Payload<T extends A_or_B> = {
[ActionTypes.FETCH_SUCCEEDED]: {
id: string;
elevation: T['elevation'];
timestamp: number;
};
...
Но я все еще получаю ошибку, так что теперь я совершенно сбит с толку.
Аргумент типа
{ elevation: T["elevation"]; }
не может быть присвоен параметру типаPartial<T>
Я действительно не могу этого объяснить.
Возможно, вместо этого вам придется сделать утверждение. (Не беспокойтесь об исправлении выше).
return updateObject<{elevation: number}>(stpt, {
elevation: action.payload.elevation,
}) as T;
Мы избегаем ошибок внутри updateObject
функции, устанавливая значение generic T
этой функции {elevation: number}
равным . Оба stpt
(типа T
) и {elevation: number}
могут быть назначены этому типу.
Но теперь возвращаемый тип updateObject
функции {elevation: number}
вместо T
. Поэтому нам нужно утверждать as T
, чтобы изменить тип.
Комментарии:
1. Хорошее объяснение, шаг за шагом
2. @Linda Paise Спасибо вам! Я больше не вижу проблемы в своих
POINT_UPDATED
действиях. Тем не менее, я добавил дополнительное действие, и это по-прежнему проблематично. Я добавил ссылку на игровую площадку TS в качестве правки к своему исходному сообщению. Если у вас будет возможность, вы можете взглянуть на это?3. @Linda Paise По какой-то причине TS Playground отключила большую часть моего кода. Вместо этого я прикрепил ссылку CodeSandbox.
4. Кастинг
as T
кажется неправильным, но я полагаю, что это придется сделать. Я понимаю ваше объяснение из вашей правки. Спасибо.