首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在打字本中推断正确的泛型类型

在打字本中推断正确的泛型类型
EN

Stack Overflow用户
提问于 2022-01-11 12:11:33
回答 2查看 1K关注 0票数 0

我有以下方法,用于用相同类型的其他对象扩展现有的键值映射(例如db查询的结果)。

代码语言:javascript
复制
export function extendWith<
  T extends { id: string | number },
  O =
    | (T["id"] extends string | number ? Record<T["id"], T> : never)
    | (T["id"] extends string | number ? Partial<Record<T["id"], T>> : never)
>(obj: O, vals: T | T[]): O {
  const extended = { ...obj };
  const values = Array.isArray(vals) ? vals : [vals];

  for (const val of values) {
    if (val !== undefined) {
      const prop = val["id"];

      if (
        typeof prop === "string" ||
        typeof prop === "number" ||
        typeof prop === "symbol"
      ) {
        (extended as any)[prop] =
          prop in obj ? { ...(obj as any)[prop], ...val } : val;
      }
    }
  }

  return extended;
}

当我按如下方式调用它时,一切都很好,即我在最后一行得到一个类型记录错误,正确地说明我要传入的对象的name类型是错误的。

代码语言:javascript
复制
interface Photo {
  id: number;
  name: string;
}
const photos: { [key: number]: Photo } = {
  1: { id: 1, name: "photo-1" },
  2: { id: 2, name: "photo-2" }
};
const extendedPhotos = extendWith<Photo>(photos, { id: 4, name: 3 });

现在,当我在最后一行的<Photo>调用中删除显式参数extendWith时,类型记录错误就消失了。我认为这与类型抄本泛型推理有关。

有没有人知道怎样才能达到正确的推论?任何让我走上正确道路的建议都是非常感谢的!

一个这里提供可玩的沙箱

EN

回答 2

Stack Overflow用户

发布于 2022-01-11 12:53:52

首先,您的第一个重载看起来是不必要的,因为您是说,当id是字符串或数字时,返回的对象要么是Partial<O>,要么是完整的OO在映射到Partial<O>类型时总是有效的,所以您只需说它的类型是Partial<O>

关于推断,如果让TypeScript推断类型,它将使用您的输入来推断函数的输出。您所要求的似乎是,函数的第二个参数必须是Photo类型的,这不是推理,除非您传递一个Photo类型的变量,否则不能推断它。要想推断出TS,您需要将最后一行替换为以下内容:

代码语言:javascript
复制
const myPhoto: Photo { id: 4, name: 'my-photo' };
const extendedPhotos = extendWith<Photo>(photos, myPhoto);

这样,TypeScript就可以使用输入值中的信息来推断输出值。

票数 0
EN

Stack Overflow用户

发布于 2022-01-11 20:44:17

这是可以做到的(尽管这个例子需要扩展):

代码语言:javascript
复制
type Index = string | number;
type ObjectWithIdIndex = { id: Index };

function extendWith<O extends Record<Index, ObjectWithIdIndex>>(obj: O, v: O[keyof O]): O {
    (obj as Record<Index, ObjectWithIdIndex>)[v.id] = v;

    return obj;
}

这些参数的类型可简化为:

  • 要求obj是一个具有id属性值的对象。我们只能通过接受扩展Record<Index, ObjectWithIdIndex>的类型来限制这一点。因此,下面应该会给出一个类型错误:
代码语言:javascript
复制
extendWith(null, { id: 3, name: "photo-4" }); 
extendWith({ abc: 1 }, { id: 3, name: "photo-4" });
  • 要求vobj值的类型相同。我们可以用O[keyof O]来限制这一点。因为keyOfobj属性的合并,所以O[keyof O]是这些属性的值的联合。以下内容还应提供一个类型错误:
代码语言:javascript
复制
interface Photo { id: number; name: string; }

const photos: Record<number, Photo> = {
  1: { id: 1, name: "photo-1" },
  2: { id: 2, name: "photo-2" }
};

extendWith(photos, { id: 4, name: "photo-4", abc: 2 });
extendWith(photos, { id: 4, name: 1 });
extendWith(photos, { name: "abc" });
extendWith(photos, null);

当调用extendWith时,类型记录将推断出比Record<Index, ObjectWithIdIndex>更具体的obj类型。这样做的结果是:

  • 我们可以使用这种类型来推断obj值的类型,然后约束v
  • 我们不能再为obj分配新的属性了,因为类型记录不知道obj是否仍然是可扩展的记录类型(例如。{ a: 1 }Record<string, number>的一个子类型,但是新的属性不能分配给它)。但是,我们可以将obj转换回它更通用的类型,然后扩展它。
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70666598

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档