首页 > 解决方案 > 如何正确缩小包含“对象键或函数”的函数参数

问题描述

我正在尝试创建一个通用类型“使用字符串键或回调获取属性”函数,并且我已经碰壁让 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[string]aT[P]的地方。

有没有办法正确指定这个,或者我只是叹息并抑制错误?

标签: typescript

解决方案


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 字符串会自动创建与stringtype的交集

解决方案是使用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


推荐阅读