网站搜索

Typescript - 正确地给带可选参数的函数添加类型


假设我们有以下函数:

function test<S, T>(obj: S, prop: keyof S, mapper?: (value: S[keyof S]) => T): S[keyof S] | T {
  return typeof mapper === 'function'
    ? mapper(obj[prop])
    : obj[prop];
}

如果我不使用 mapper 参数,那么返回值的类型不会被正确推断:

const value = test({ a: 'stringValue' }, 'a'); // value is of type "unknown"

但是如果我提供一个身份函数作为第三个参数,它会被正确推导:

const value = test({ a: 'stringValue' }, 'a', x => x); // value is of type "string"

当我们不提供 mapper 参数时,test 函数应该如何定义类型,以便返回值的类型能够被正确推断?

操场链接

答案#1

只需使用函数重载!

function test<S, T>(obj: S, prop: keyof S): S[keyof S];
function test<S, T>(obj: S, prop: keyof S, mapper: (value: S[keyof S]) => T): T;
function test<S, T>(obj: S, prop: keyof S, mapper?: (value: S[keyof S]) => T): S[keyof S] | T {
    return typeof mapper === 'function'
        ? mapper(obj[prop])
        : obj[prop];
}

const value = test({ a: 'stringValue' }, 'a'); // string

const value2 = test({ a: 'stringValue' }, 'a', x => x);  // string

操场

答案#2

通常的方法是通过将更多信息移动到函数的泛型变量来帮助编译器。这对你有效吗?(Playground)

function test<
    Obj,
    Prop extends keyof Obj,
    Mapper extends ((value: Obj[Prop]) => any) | undefined
>(
    obj: Obj,
    prop: Prop,
    mapper?: Mapper
): undefined extends Mapper ? Obj[Prop] : ReturnType<NonNullable<Mapper>> {
    return mapper ? mapper(obj[prop]) : obj[prop];
}

const value1 = test({ a: "123" }, "a");

const value2 = test({ a: "123" }, "a", x => parseInt(x));

可选的映射器使代码更难编写,但我相当确定这段代码可以进一步简化。