如何用精确泛型类型制作Typescript Reducer

2022-01-30 11:36:48 标签 typescriptgenericsreducers

问题

我有两个简化器,它们对包含点的两个不同数组使用完全相同的逻辑。数组只有一种类型的点;这些类型是A和B,我想要创建一个reducer,它接受类型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>。

CodeSandbox链接

###我开始试图填补你的代码中缺失的部分,以找到问题所在。这么做很明显问题出在你的Action type (as suggested by @Alex Chashin).

类型(由@Alex Chashin建议)。

我知道Actions有一个需要包含POINT_UPDATED的类型。它也有有效载荷。有效负载包括一个id、一个类别和一些newValues。

type Actions = {
    type: typeof POINT_UPDATED;
    payload: {
        id: string;
        category: Category;
        newValues: ????;
    }
}

newValues是什么?根据updateObject的签名,我们知道它应该是Partial<T>。但是action不知道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上:

类型“T”上不存在属性“id”

你需要细化T的类型,这样它就知道id属性:

const updateItemInArray = <T extends {id: string}>(

这样做会在你的genericReducer中导致新的错误,因为你说T必须有一个类别,但你没有说它必须有一个id。我们可以用以下方法来解决这个问题:

const genericReducer = <T extends (A | B) & {id: string}>(

和什么是一样的

const genericReducer = <T extends {id: string; category: Category}>(

虽然实际上你似乎从来没有看你的点T[]上的类别。所以你真正需要的是:

const genericReducer = <T extends {id: string}>(

打印稿操场上链接

编辑:

修改后的代码中出现的错误真的很愚蠢。从技术上讲,T扩展了一个带有{elevation: number}的类型。因此,它可能需要一个更具体的类型版本,如{elevation: 99}。在这种特殊情况下,{elevation: number}将不能赋值给Partial<{elevation: 99}>。

至少我认为这就是问题所在。然而,我的第一个修复没有工作。我试图细化action的类型,以说明elevation属性必须与来自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函数的泛型T设置为{elevation: number},我们可以避免updateObject函数中的错误。stpt(类型T)和{elevation: number}都可以赋值给该类型。

但是现在updateObject函数的返回类型是{elevation: number}而不是T。所以我们需要断言为T来改变类型。

阅读全文

▼ 版权说明

相关文章也很精彩
推荐内容
更多标签
相关热门
全站排行
随便看看

错说 cuoshuo.com —— 程序员的报错记录

部分内容根据CC版权协议转载;网站内容仅供参考,生产环境使用务必查阅官方文档

辽ICP备19011660号-5

×

扫码关注公众号:职场神器
发送: 1
获取永久解锁本站全部文章的验证码