如何正确缩小包含"an object key or a function"的函数参数



我正在尝试制作一个泛型类型"使用字符串键或回调获取属性"函数,并且我遇到了障碍,让 TS 将我的类型参数缩小到包含对象的键。

函数如下:

function get<T, V>(value: T, fn: (value: T) => V): V;
function get<T, P extends keyof T>(value: T, prop: P): T[P];
function get<T, P extends keyof T>(value: T, prop: P | ((value: T) => any)):
typeof prop extends (o: any) => infer V ? V : T[P] {
switch (typeof prop) {
case 'function':
return prop(value);
case 'string':
return value[prop]; // ERROR HERE
default: throw new TypeError('Property getter must be string or function');
}
}

编译器在string分支中抱怨 - 显然prop缩小到不是P,而是P & string,这在这里不能使用,因为它意味着尝试返回需要T[P]T[string]

有没有办法正确指定这一点,还是我只是叹息并抑制错误?

function get<T, V>(value: T, fn: (value: T) => V): V;
function get<T, P extends keyof T>(value: T, prop: P): T[P];
function get<T, P extends keyof T>(value: T, prop: P | ((value: T) => any)) {
if (typeof prop === 'function') {
return prop(value);
}
if (prop in value) {
return value[prop];
}
throw new TypeError('Property getter must be string or function');
}
const a = get(1, (a) => a + 1);
const b = get({a: 'a'}, 'a')
console.log(a); // 2
console.log(b);// "a"

解释。在之前的实现中几乎没有问题:

  • 返回类型已在重载中定义,无需在 实现
  • 选中 Typeof 字符串会自动创建与string类型的交集

解决方案是使用key in object语法,这为我们提供了V[P]的类型规范,第二个分支正在检查typeof function


与字符串交集的确切问题是,原始实现无法处理所有可能的键类型,它们是 -number | string | symbol。对于字符串作为键以外的任何内容,该函数将引发异常。请考虑以下示例:

// symbol prop example
const symbolProp = Symbol()
const v = get({[prop]: 'value'}, prop);
// array example
const v2 = get([1, 2], 1);
// object with number key example
const v3 = get({1: 'value'}, 1);

所有三个示例的类型都是正确的,但会抛出错误,因为 key 不是字符串。对于我提出的解决方案,所有这些都可以正常工作。键区别是prop in value,它确保 prop 是值键,但不需要特定类型的键。


如果我们真的想确保我们只需要字符串键,那么函数类型定义应该反映这一点。考虑:

function get<T, V>(value: T, fn: (value: T) => V): V;
function get<T, P extends keyof T & string>(value: T, prop: P): T[P];
function get<T, P extends keyof T & string>(value: T, prop: P | ((value: T) => any)) {
switch (typeof prop) {
case 'function':
return prop(value);
case 'string':
return value[prop];
default: throw new TypeError('Property getter must be string or function');
}
}

核心区别在于 -P extends keyof T & string我们在类型级别上说我们只接受 P 的键,它们也是字符串。该方法与我们检查typeof string的实现一致。

不需要仔细检查

function get<T, P extends keyof T>(value: T, prop: P | ((value: T) => any)):
T[P] {
switch (typeof prop) {
case 'function':
return prop(value);
case 'string':
return value[prop]; // ERROR HERE
default: throw new TypeError('Property getter must be string or function');
}
}

演示

最新更新