我有一个带有已知键的 const 对象。每个项目可能有也可能没有特定的属性。 我想要一个函数,给定键,返回值及其适当的类型。
我正在使用 TypeScript 4.6.2。
当然,我可以只输入
data.key?.property
,但我需要以编程方式执行此操作。
这是一个人为的例子来展示我所追求的:
const data = {
alice: {loves: 3},
bob: {loves: 'hippos'},
charlie: {hates: 'toast'},
denise: {loves: (a:number) => `hello`}
} as const;
function personLoves(name:keyof typeof data) {
const item = data[name]
const loves = 'loves' in item ? item.loves : undefined;
// ^ this line is clearly not right!
return loves;
}
const aliceLoves = personLoves('alice');
// I want `aliceLoves` to be type number.
const bobLoves = personLoves('bob');
// I want `bobLoves` to be type string or 'hippo'
const charlieLoves = personLoves('charlie');
// I want `charlieLoves` to be type undefined
const deniseLoves = personLoves('denise');
// I want `deniseLoves` to be type (a:number) => string
在这种情况下,我建议在 personLoves
上使用
通用签名来捕捉
name
是 data
的 特定键的想法。这将允许 TypeScript 根据调用函数时传递的值来跟踪正确的关联返回类型。
与此相结合,我将使用重载签名来捕获您所遇到的两种情况:当人员的数据包含
loves
键时(在这种情况下,我们从该键值的类型推断返回类型) ),而当它不存在时(在这种情况下,返回类型始终为 undefined
)。
// A helper type that we will need to figure out which keys have values with a 'loves' property
type KeysExtending<O, T> = {
[K in keyof O]: O[K] extends T ? K : never;
}[keyof O];
// Then, the keys of `data` which DO have a 'loves' key are:
type KeysWithLoves = KeysExtending<typeof data, {loves: unknown}> // resolves to: "alice" | "bob" | "denise"
// Then, write one overload case for keys in the above type, and another for everything else.
// Since overloads always try to match to the earliest listed overload, this will first capture the properties that DO have a 'loves' key, and infers the correct return type from that.
// Anything not caught by the first overload must be a name without a 'loves' key in its data, so we give it return type 'undefined'.
function personLoves<T extends KeysWithLoves>(name: T): (typeof data)[T]["loves"];
function personLoves(name: keyof typeof data): undefined;
function personLoves(name: keyof typeof data) {
const item = data[name]
const loves = 'loves' in item ? item.loves : undefined;
return loves;
}
这似乎得到了你想要的结果:
const aliceLoves = personLoves('alice');
// Is the literal number type: 3.
const bobLoves = personLoves('bob');
// Is the literal string type: 'hippo'
const charlieLoves = personLoves('charlie');
// Is the type: undefined
const deniseLoves = personLoves('denise');
// Is the type: (a:number) => string