简体   繁体   English

基于条件类型的 Typescript 中的不同 object 字段

[英]Different object fields in Typescript based on conditional type

I want to write a function (eventual use is a React function component) in Typescript that takes a props object with a list of list of objects of any type. I want to write a function (eventual use is a React function component) in Typescript that takes a props object with a list of list of objects of any type. Assume the function should print a "key" for each, where if the object type has an id field then the id value is printed as the key, and if the object type doesn't have an id field, the key will be derived from an accessor function in the props object (pseudocode here with types omitted):假设 function 应该为每个打印一个“密钥”,其中如果 object 类型有一个 id 字段,那么 id 值将打印为键,如果 ZA8CFDE6331BD59EB2AC96F 类型不是从 8911C4B666 派生的字段,则道具 object 中的访问器 function (此处为伪代码,省略类型):

function process(props: { items: ..., ...}) {
  props.items.forEach(item => {
    if (item.id) {
      console.log(`Key for ${item.id}`)
    } else {
      console.log(`Key for ${props.keyFunction(item)}`)
    }
  })
}

process({items: [{id: "1", name: "A"}, {id: "2", name: "B"}]})
process({items: ["A", "B"], keyFunction: (item) => item})

Ideally, I'd like the following:理想情况下,我想要以下内容:

  1. Typescript should error if keyFunction is provided but the items already have an id keyFunction如果提供了 keyFunction 但项目已经有 id 应该会出错
  2. Typescript should error if the items don't have an id and keyFunction isn't provided如果项目没有 id 且未提供 keyFunction, keyFunction应该出错
  3. Typescript should know about keyFunction and id in the appropriate places inside the process function body (autocomplete should work) Typescript 应该知道process内适当位置的 keyFunction 和 id function 主体(自动完成应该可以工作)

Is there a way to write the types for this function so that all 3 of the above work?有没有办法为此 function 编写类型,以便上述所有 3 个工作?

Note: I understand that if these were parameters instead of values of a config object, I could use conditional function overloads, but because the actual use case for this is a React function component with props, that won't work here.注意:我知道如果这些是参数而不是配置 object 的值,我可以使用有条件的 function 重载,但是因为实际用例是 React ZC1C425268E68385D14AB5074C17Z 组件,所以这里的组件不会'4

What I've tried我试过的

I've tried using a conditional type, which works at the callsite, but I can't figure out how to make Typescript know about keyFunction correctly playground link :我尝试使用在调用点有效的条件类型,但我不知道如何让keyFunction正确了解 keyFunction 游乐场链接

type KeyProps<T> = T extends { id: string }
  ? { items: T[] }
  : {
      items: T[];
      keyFunction(item: T): string;
    };

function process<T>(props: KeyProps<T>) {
  props.items.map(item => {
    if (item.id) {
      console.log(`Key for ${item.id}`)
    } else {
      console.log(`Key for ${props.keyFunction(item)}`)
    }
  })
}

I've also tried using a discriminated union, but I don't know how to provide a type constraint to only one branch of the union with a generic:我也尝试过使用有区别的联合,但我不知道如何为具有泛型的联合的一个分支提供类型约束:

type KeyProps<T> =
  | { type: "autokey", items: T[] } // How to provide that T should extend { id: string } here?
  | { type: "normal", items: T[], keyFunction(item: T): string }

Note: This answer assumes that it isn't important that the two parameters are contained inside of a wrapper props object.注意:此答案假定这两个参数包含在包装器道具 object 内并不重要。

Ideally, I'd like the following:理想情况下,我想要以下内容:

  1. Typescript should error if keyFunction is provided but the items already have an id keyFunction如果提供了 keyFunction 但项目已经有 id 应该会出错
  2. Typescript should error if the items don't have an id and keyFunction isn't provided如果项目没有 id 且未提供 keyFunction, keyFunction应该出错
  3. Typescript should know about keyFunction and id in the appropriate places inside the process function body (autocomplete should work) Typescript 应该知道process内适当位置的 keyFunction 和 id function 主体(自动完成应该可以工作)

Okey, so requirement two (2) can be solved by simply using function overloads.好的,因此只需使用 function 过载即可解决要求二 (2)。 Requirement three (3) can be solved by moving the implementation logic into their own type safe functions.可以通过将实现逻辑移动到它们自己的类型安全函数中来解决要求三 (3)。 However, requirement one (1) is quite tricky.但是,要求一 (1) 非常棘手。 We can easily infer the type of items using a generic T .我们可以使用泛型T轻松推断items的类型。 Then using T we can derive a type D such that D does not contain any object types with a key of id .然后使用T我们可以派生一个类型D ,使得D不包含任何具有id键的 object 类型。 The tricky part is managing to both derive the base type T from items while also constraining the type of items to D .棘手的部分是设法从items派生基本类型T ,同时将items的类型限制为D The way I've done it is by using an intersection type T[] & D[] for the type of items .我这样做的方法是使用交叉类型T[] & D[]作为items的类型。 This will infer the base type T from elements in the items array, while also constraining the type of the elements in the items array.这将从items数组中的元素推断出基本类型T ,同时也限制items数组中元素的类型。

interface ObjWithID {
    [k: string]: any;
    id: string;
}

type AnyWithoutID<T> = T extends { id: any } ? never : T;

function processItemsWithID(items: ObjWithID[]) {
  items.forEach(item => console.log(`Key for ${item.id}`))
}

function processItemsWithoutID<T, D extends AnyWithoutID<T>>(items: T[] & D[], keyFunction: (value: T) => string) {
  items.forEach(item => console.log(`Key for ${keyFunction?.(item)}`))
}

function process(items: ObjWithID[]): void;
function process<T, D extends AnyWithoutID<T>>(items: T[] & D[], keyFunction: (value: T) => string): void;
function process(items: any[], keyFunction?: (value: any) => any) {
  if (keyFunction) {
    return processItemsWithoutID(items, keyFunction);
  } else {
    return processItemsWithID(items);
  }
}

process([{id: "1", name: "A"}, {id: "2", name: "B"}])
process([{id: "1", name: "A"}, {id: "2", name: "B"}], (item) => item)
process([{ name: "A"}, { name: "B"}], (item) => item.name)
process(["A", "B"], (item) => item)

playground 操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM