如何使用精确的通用类型制作 Typescript Reducer
How to Make a Typescript Reducer with Exact Generic Types
问题
我有两个减速器,它们对包含点的两个不同数组使用完全相同的逻辑。数组将只有一种类型的点;这些类型是 A
和 B
。我想创建一个 reducer,它接受一个类型为 A
或类型为 B
的点数组,然后是 returns 一个相同类型的数组。
当我尝试编译下面的代码时,出现此错误:
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
这是我根据 Linda Paiste 的回答所做的最新尝试的 CodeSandbox link。此时仍然存在分配给类型 Partial<T>
.
的问题
我开始尝试填写您的代码中缺失的部分,以便找出问题所在。这样做,很明显问题出在您的 Action
类型中(如@Alex Chashin 所建议)。
我知道 Actions
有一个 type
需要包含 POINT_UPDATED
。它还有一个 payload
。有效负载包括一个 id
、一个 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
那么 newValues 必须是 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[]
...
我在尝试访问 item.id
时在您的 updateItemInArray
上看到一个错误:
Property 'id' does not exist on type 'T'
你需要细化 T
的类型,这样它就知道 id 属性:
const updateItemInArray = <T extends {id: string}>(
这样做会导致您的 genericReducer
出现新的错误,因为您已经说过 T
必须有一个 category
但您没有说它必须有一个 id
.我们可以解决这个问题:
const genericReducer = <T extends (A | B) & {id: string}>(
与
相同
const genericReducer = <T extends {id: string; category: Category}>(
尽管实际上看起来您从来没有看过 T[]
点上的 category
。所以你真正需要的是:
const genericReducer = <T extends {id: string}>(
编辑:
您在修改后的代码中遇到的错误真的很愚蠢。 技术上 T
extends
{elevation: number}
的类型。因此,它可能需要更具体的类型版本,例如 {elevation: 99}
。在那种情况下,{elevation: number}
将不能分配给 Partial<{elevation: 99}>
.
至少我认为是问题所在。但是我的第一个修复程序没有用。我试图细化 Actions
的类型,说 elevation
属性 必须与 T
.
中的匹配
type Payload<T extends A_or_B> = {
[ActionTypes.FETCH_SUCCEEDED]: {
id: string;
elevation: T['elevation'];
timestamp: number;
};
...
但我仍然遇到错误,所以现在我完全糊涂了。
Argument of type { elevation: T["elevation"]; }
is not assignable to parameter of type Partial<T>
我真的无法解释那个。
您可能不得不做出断言。 (不要为上面的修复而烦恼)。
return updateObject<{elevation: number}>(stpt, {
elevation: action.payload.elevation,
}) as T;
我们通过将该函数的通用 T
设置为 {elevation: number}
来避免 updateObject
函数中的错误。 stpt
(类型 T
)和 {elevation: number}
都可分配给该类型。
但是现在 updateObject
函数的 return 类型是 {elevation: number}
而不是 T
。所以我们需要断言 as T
来改变类型。
问题
我有两个减速器,它们对包含点的两个不同数组使用完全相同的逻辑。数组将只有一种类型的点;这些类型是 A
和 B
。我想创建一个 reducer,它接受一个类型为 A
或类型为 B
的点数组,然后是 returns 一个相同类型的数组。
当我尝试编译下面的代码时,出现此错误:
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
这是我根据 Linda Paiste 的回答所做的最新尝试的 CodeSandbox link。此时仍然存在分配给类型 Partial<T>
.
我开始尝试填写您的代码中缺失的部分,以便找出问题所在。这样做,很明显问题出在您的 Action
类型中(如@Alex Chashin 所建议)。
我知道 Actions
有一个 type
需要包含 POINT_UPDATED
。它还有一个 payload
。有效负载包括一个 id
、一个 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
那么 newValues 必须是 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[]
...
我在尝试访问 item.id
时在您的 updateItemInArray
上看到一个错误:
Property 'id' does not exist on type 'T'
你需要细化 T
的类型,这样它就知道 id 属性:
const updateItemInArray = <T extends {id: string}>(
这样做会导致您的 genericReducer
出现新的错误,因为您已经说过 T
必须有一个 category
但您没有说它必须有一个 id
.我们可以解决这个问题:
const genericReducer = <T extends (A | B) & {id: string}>(
与
相同const genericReducer = <T extends {id: string; category: Category}>(
尽管实际上看起来您从来没有看过 T[]
点上的 category
。所以你真正需要的是:
const genericReducer = <T extends {id: string}>(
编辑:
您在修改后的代码中遇到的错误真的很愚蠢。 技术上 T
extends
{elevation: number}
的类型。因此,它可能需要更具体的类型版本,例如 {elevation: 99}
。在那种情况下,{elevation: number}
将不能分配给 Partial<{elevation: 99}>
.
至少我认为是问题所在。但是我的第一个修复程序没有用。我试图细化 Actions
的类型,说 elevation
属性 必须与 T
.
type Payload<T extends A_or_B> = {
[ActionTypes.FETCH_SUCCEEDED]: {
id: string;
elevation: T['elevation'];
timestamp: number;
};
...
但我仍然遇到错误,所以现在我完全糊涂了。
Argument of type
{ elevation: T["elevation"]; }
is not assignable to parameter of typePartial<T>
我真的无法解释那个。
您可能不得不做出断言。 (不要为上面的修复而烦恼)。
return updateObject<{elevation: number}>(stpt, {
elevation: action.payload.elevation,
}) as T;
我们通过将该函数的通用 T
设置为 {elevation: number}
来避免 updateObject
函数中的错误。 stpt
(类型 T
)和 {elevation: number}
都可分配给该类型。
但是现在 updateObject
函数的 return 类型是 {elevation: number}
而不是 T
。所以我们需要断言 as T
来改变类型。