使用TypeScript,我可以键入getProperty〈T,K extends keyof T>的curried版本吗?

lbsnaicq  于 2023-04-22  发布在  TypeScript
关注(0)|答案(4)|浏览(125)

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)
h6my8fg2

h6my8fg21#

编辑TypeScript〉= 4.1.5

const makeGetter = <TKey extends string>(key: TKey) => <TObject extends { [P in TKey]?: unknown }>(object: TKey extends keyof TObject ? TObject : `${TKey} is missing as property of object`) => (object as TObject)[key];

const getId = makeGetter('id');

const a: unknown = getId({})
const b: number = getId({id: 1})
const c: number | undefined = getId({} as { id?: number})

编译器将用一个有用的错误消息来抱怨getId({})
使用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
}
i1icjdpr

i1icjdpr2#

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
gwo2fgha

gwo2fgha3#

如果你把它分成两个步骤,它可以是最小的冗长,同时是完全类型安全的:

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
f45qwnt8

f45qwnt84#

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 })

相关问题