С помощью TypeScript можно ли ввести каррированную версию getProperty
Пример из 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)
4 ответа:
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
Если вы разделите его на двухэтапный процесс, он может быть минимально подробным и полностью типобезопасным одновременно:
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
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
выводится правильно как число. Единственное, что вы получитеany
, Если объект, переданный вgetId
, не имеет свойстваid
, поэтому это не строгое, но в целом элегантное решение.EDIT: с момента написания этого ответа я узнал, что тип
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 })