【问题标题】:Infer type depending on other field type根据其他字段类型推断类型
【发布时间】:2021-11-04 18:05:07
【问题描述】:

我正在实现一个自定义表格组件,开发人员可以在其中自定义某些单元格数据的呈现方式。为了使其更加万无一失,我试图让 TS 根据在id 属性中选择的User 属性推断render() 参数的类型到该属性的类型。

interface Col<T extends Record<string, any>> {
  id: (keyof T & string);
  render: (data: T[this["id"]]) => string;
}

interface User {
  name: string;
  enabled: boolean;
}

const cols: Array<Col<User>> = [
  {
    id: "name",
    render: (name: string) => name;
  },
  {
    id: "enabled",
    render: (enabled: boolean) => enabled ? "yes" : "no";
  }
];

但产生以下错误:

      TS2322: Type '(name: string) => string' is not assignable to type '(data: string | boolean) => string'.
  Types of parameters 'name' and 'data' are incompatible.
    Type 'string | boolean' is not assignable to type 'string'.
      Type 'boolean' is not assignable to type 'string'.

这是因为render() 的第一个参数被推断为联合string | boolean(如果User 接口包含更多类型,则更多类型)。我期待 T[this["id"]] 返回在 this.id 属性中选择的实际属性类型,但它似乎扩展为所有可能选择的联合。

有没有办法在不向Col 添加另一个泛型参数的情况下使类型解析动态化?

【问题讨论】:

    标签: typescript


    【解决方案1】:

    为此,您需要创建所有允许状态的联合:

    interface User {
        name: string;
        enabled: boolean;
    }
    
    type Values<T> = T[keyof T]
    
    type Union<T> = Values<{
        [Prop in keyof T]: {
            id: Prop,
            render: (name: T[Prop]) => string
        }
    }>
    
    type Result = Union<User>[]
    
    const cols: Result = [
        {
            id: "name",
            render: (name /** string */) => name
        },
        {
            id: "enabled",
            render: (enabled /** boolean */) => enabled ? "yes" : "no"
        }
    ];
    

    Playground

    Union - 遍历每个User 属性并创建一个对象:

    {
     id: Prop,
     render: (value: User[Prop])=>string
    }
    

    这个对象对我们很重要。为了获得它,我们应该使用Values helper。它返回所有对象值的联合。

    【讨论】:

    • 谢谢!你对 TS 有不可思议的了解!当有人将Record&lt;string, any&gt; 传递为T 时,有没有办法处理这种情况?在这种情况下,在迭代 T 键期间,只有第一列被“记住”并触发错误。
    • @IonBazan 如果有人提供 Record,你期望什么结果?
    • @IonBazan 你想禁止通过Record&lt;string, any&gt; 吗?
    • @IonBazan 它的行为与您描述的完全一样。尝试用Record&lt;string, any&gt;创建一个数组
    猜你喜欢
    • 2020-08-30
    • 1970-01-01
    • 1970-01-01
    • 2019-04-15
    • 2021-09-21
    • 2020-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多