【问题标题】:With TypeScript, can I type a curried version of getProperty<T, K extends keyof T>使用 TypeScript,我可以键入 getProperty<T, K extends keyof T> 的柯里化版本吗
【发布时间】:2018-05-30 13:58:08
【问题描述】:

来自https://www.typescriptlang.org/docs/handbook/advanced-types.html的示例

function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
    return o[name]; // o[name] is of type T[K]
}

咖喱版:

function curriedGetProperty<T, K extends keyof T>(name: K): (o: T) => T[K] {
    return (o: T) => o[name]; // o[name] is of type T[K]
}

const record = { id: 4, label: 'hello' }

const getId = curriedGetProperty('id') // Argument of type '"id"' is not assignable to parameter of type 'never'.

const id = getId(record)

【问题讨论】:

    标签: typescript generics currying


    【解决方案1】:
    const getProperty = <P extends string>(prop: P) => <O extends any>(obj: O) => obj[prop]
    
    const record = { id: 4, label: 'hello' }
    
    const getId = getProperty('id')
    
    const id = getId(record)
    

    这似乎有效。 id 的类型被正确推断为数字。唯一的问题是,如果传递给getId 的对象没有id 属性,您将收到any,因此它并不严格,而是一个整体优雅的解决方案。

    编辑:自从写了这个答案,我了解到Record 类型可用于指定需要特定键的对象类型。利用这些知识,我们可以编写一个类型安全、简洁、易读的解决方案:

    // implementation
    const get = <K extends string>(key: K) => <V>(obj: Record<K, V>) => obj[key]
    
    // usage
    const person = {
      name: "kingdaro",
      age: 21,
    }
    
    const fruit = {
      type: "apple",
      color: "red",
    }
    
    const nameGetter = get("name")
    
    nameGetter(person) // return type inferred as string
    nameGetter(fruit) // fails, fruit has no key "name"
    
    // minor caveat: when passing an object literal, the extra key will raise an error
    // you can declare the object separately to sidestep around this
    // but this wouldn't come up often anyway
    nameGetter({ name: "kingdaro", age: 21 })
    

    【讨论】:

    • 这完全绕过了类型系统。您现在可以在任何对象上调用它,无论它是否具有正确的属性。你可能一开始就不使用 TypeScript。
    • 不完全是@dave-cousineau。是的,您可以将任何内容传递给getId。但是,将相应地键入结果。如果您通过getId({}),您将获得any。但是,如果您传递getId({id: 4}),则结果将输入为数字。或者如果你通过getId({id: undefined} as Partial&lt;{id: number}&gt;),你会得到number | undefined
    • @chautelly getId 可以用任何东西调用,但getProperty 也可以用任何东西调用。但如果使用正确,它确实会返回正确的类型。
    • 使用@kingdaro 的答案,getId(reocrd) 为我返回一个number 类型
    【解决方案2】:

    如果您将其拆分为两步过程,它可以同时保持最低限度的冗长和完全类型安全:

    interface recordType {
       id: number,
       label: string
    }
    
    const record = { id: 4, label: 'hello' };
    
    const getPropertyBuilder = function <T>() {
       return <K extends keyof T>(key: K) => (o: T) => o[key];
    };
    
    const propertyBuilder = getPropertyBuilder<recordType>();
    const getId = propertyBuilder('id'); // getId is (o: recordType) => number
    const id = getId(record); // id is number
    
    // or in one go
    const label = getPropertyBuilder<recordType>()('label')(record); // label is string
    

    如前所述,也可与Partial 一起使用:

    const propertyBuilder = getPropertyBuilder<Partial<typeof record>>();
    const getId = propertyBuilder('id');
    const id = getId(record); // id is number
    const id2 = getId({ id: 3 }); // also number
    

    【讨论】:

    • 是的,这也有效。除了getId 现在不太灵活。我将无法通过Partial&lt;typeof record&gt;
    • 另外,function curriedGetProperty&lt;T, K extends keyof T&gt;(name: K) { 必须以这种方式输入,const getId = curriedGetProperty&lt;Rec, 'id'&gt;('id');。否则getId 的结果是number | string
    • @chautelly 似乎无法避免类型为string | number,即使使用K
    • 它确实对我有用。您是否像这样打电话给curriedGetPropertycurriedGetProperty&lt;Rec, 'id'&gt;('id');
    • 好的,是的,您将类型缩小为“id”,结果也缩小了。
    【解决方案3】:
    type WithProp<T extends any, K extends string> = { [P in K]: T[P] }
    
    function curriedGetProperty <P extends string>(prop: P) {
      return <T, O extends WithProp<T, typeof prop>>(o: O) => {
        return o[prop]
      }
    }
    

    似乎打字更安全。

    const getId = curriedGetProperty('id')
    getId({id: 'foo'}) // returns string
    getId({label: 'hello'}) // fails
    

    【讨论】:

    • 但是,这不适用于Partial。除非您通过在: 之前添加? 来使WithPropPartial 一样
    • 这允许getId({}) 返回any。但不允许getId(43434)getId(null)
    • 我认为这和我的差不多,虽然你的不需要第二步,所以这实际上可能更好。你说它允许getId({}) 但对我来说不允许。你说它不允许getId(null),但对我来说它允许。我认为如果不启用严格的空检查,您无法避免通过nullundefined。在您使用它之前,您的也不会绑定到特定类型 T,这使得属性 getter 可用于任何对象上的强类型,这也更好。这是现在最好的答案。
    • 是的,我确实为我的项目启用了strictNullChecks
    【解决方案4】:

    使用 TypeScript 3.0.3 我能够做到这一点:

    function composeGetter<K extends string>(prop: K) {
        function getter<T extends { [P in K]?: any }>(object: T): T[typeof prop]
        function getter<T extends { [P in K]: any }>(object: T) {
            return object[prop]
        }
    
        return getter
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-21
      • 1970-01-01
      • 1970-01-01
      • 2017-07-25
      • 1970-01-01
      • 2018-12-27
      • 2021-08-29
      相关资源
      最近更新 更多