【问题标题】:Infer a type key from another object's values从另一个对象的值推断类型键
【发布时间】:2020-07-19 17:13:13
【问题描述】:

我有一个界面专栏。

interface column {
  field: string
  label: string
}

所以一行列将是:

const cols = [{
    label: 'First Name',
    field: 'fname',
  },
  {
    label: 'Last Name',
    field: 'lname',
  },
  {
    label: 'Email',
    field: 'email',
  }]

Data 将是一个数据行数组。

const data:{}[] = [{
fname: 'bob', lname: 'dylan', email: 'db@email.com',
fname: 'van', lname: 'halen', email: 'vh@email.com'
}]

有没有办法强制数据的键,即 [fname, lname, email] 为对应的 cols[n]['field'] 的值? 提前致谢。

【问题讨论】:

    标签: javascript typescript type-inference


    【解决方案1】:

    是的,只要您定义cols 以使编译器不会将field 属性扩展为string,您就可以这样做。使用 TS3.4+ 最简单的方法是使用const assertion

    const cols = [{
      label: 'First Name',
      field: 'fname',
    },
    {
      label: 'Last Name',
      field: 'lname',
    },
    {
      label: 'Email',
      field: 'email',
    }] as const;
    

    as const 意味着cols 将被视为只读元组,其中labelfield 属性具有string literal 类型。

    从那里您可以创建一个类型别名,将一组Column 兼容值转换为以field 属性作为键的对象类型。当然没有提到每个字段的值类型应该是什么,所以我假设它总是string

    interface Column {
      field: string
      label: string
    };
    
    type StringObjFromColumns<T extends readonly Column[]> =
      { [K in T[number]["field"]]: string }
    

    然后我们可以将你的数据类型定义为StringObjFromColumns&lt;typeof cols&gt;:

    const data: StringObjFromColumns<typeof cols>[] = [{
      fname: 'bob', lname: 'dylan', email: 'db@email.com',
    }, {
      fname: 'van', lname: 'halen', email: 'vh@email.com'
    }, {
      fname: 'van', lname: 'morrison', age: 75, email: 'vm@example.com' // error! age is extra
    }, {
      fname: 'ted', lname: 'nugent' // error! email is missing
    }]
    

    您可以看到它如何强制每个对象必须具有string 类型的这三个字段的约束。

    好的,希望对您有所帮助;祝你好运!

    Playground link to code

    【讨论】:

      【解决方案2】:

      这是不可能的,因为 typescript 类型是在编译时而不是运行时强制执行的。请参阅我对this question 的回答,它回答了您的问题。

      【讨论】:

        【解决方案3】:

        这是一个棘手的问题。

        interface Column {
            field: string
            label: string
        }
        

        我们可以定义一个RowObject,另外,为了举例,一个Row 具有column 属性和row 本身。

        interface RowObject {
            [key: string]: string;
        }
        
        interface Row {
            cols: Column[];
            object: RowObject;
        }
        

        现在,我们应该定义一个函数来制作新对象的原型。

        const createRowBlueprint = (cols: Column[]): Row => {
            return {
                cols: [...cols],
                object: cols.map(c => c.field)
                            .reduce((prop: RowObject, prev: string) => (prop[prev] = '', prop), {})
            };
        };
        

        我刚刚定义了Row 接口来动态访问“真实行对象”属性,像这样

        const row = createRowBlueprint(cols);
        row.cols.forEach(col => {
            const { field } = col;
            const value = row.object[field];
            console.log(`Field ${field} binded to ${value}`);
        });
        

        给定您的 columns 集合,这将输出以下内容

        {
          cols: [
            { label: 'First Name', field: 'fname' },
            { label: 'Last Name', field: 'lname' },
            { label: 'Email', field: 'email' }
          ],
          row: { fname: '', lname: '', email: '' }
        }
        
        Field fname binded to
        Field lname binded to
        Field email binded to
        

        显然,我将默认值设置为空字符串。但是,它有效的证明是从该字段访问的值不是undefined

        最后,这种方法不是安全类型的。但是,据我所知,Typescript 暂时不支持编译时的动态类型定义......所以这是一个棘手的解决方法。

        希望对你有帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-08-09
          • 1970-01-01
          • 2019-08-12
          • 2022-07-19
          • 2022-10-24
          • 1970-01-01
          • 2022-12-18
          • 1970-01-01
          相关资源
          最近更新 更多