С TypeScript я могу набрать карри версию getProperty<T, K расширяет ключ T>
Пример из 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
Использование 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
}
Если вы разделите его на два этапа, он может быть минимально подробным и в то же время полностью безопасным:
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
собственности на него, так что это не строгое, а общее элегантное решение.
РЕДАКТИРОВАТЬ: С момента написания этого ответа я узнал, что 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 })