首页 > 解决方案 > 避免对联合类型进行愚蠢的推断( Element 隐含地具有“任何”类型,因为类型的表达式......)

问题描述

所以我有一个以前问过的问题的变体,但不知何故,答案没有帮助。这是代码:

type ModelA = {
    type: 'a'
    method: 'foo' | 'bar'
}

type ModelB = {
    type: 'b'
    method: 'baz'
}

type Model = ModelA | ModelB

const dictionary = {
    a: {
        foo: () => 'foo',
        bar: () => 'bar'
    },
    b: {
        baz: () => 'baz'
    }
}

function getMethod(m: Model): Function {
    return dictionary[m.type][m.method] <--- error
}

Element implicitly has an 'any' type because expression of type '"foo" | "bar" | "baz"' can't be used to index type '{ foo: () => string; bar: () => string; } | { baz: () => string; }'.
  Property 'foo' does not exist on type '{ foo: () => string; bar: () => string; } | { baz: () => string; }'.

如果我像这样使用一些愚蠢的类型检查:

function getMethod(m: Model) {
    if (m.type === 'a') {
        return dictionary[m.type][m.method]
    }
    return dictionary[m.type][m.method]
}

它之所以有效,是因为在两个分支中,TS 都知道获取该方法是安全的。

我知道这应该使用通用解决,但不知何故我无法弄清楚。

标签: typescripttypescript-genericsdiscriminated-union

解决方案


为了输入它,您需要推断dictionary每个对象的属性:

type ModelA = {
    type: 'a'
    method: 'foo' | 'bar'
}

type ModelB = {
    type: 'b'
    method: 'baz'
}

type Model = ModelA | ModelB

const dictionary = {
    a: {
        foo: () => 'foo',
        bar: () => 42
    },
    b: {
        baz: () => 'baz'
    }
}

const withDict = <
    Key extends string,
    SubKey extends string,
    Fn extends (...args: any[]) => any,
    Dict extends Record<Key, Record<SubKey, Fn>>
>(dict: Dict) =>
    <
        Type extends keyof Dict,
        Method extends keyof Dict[Type],
    >({ type, method }: { type: Type, method: Method }) =>
        dict[type][method]

const getMethod = withDict(dictionary)

// () => string
const result = getMethod({ type: 'a', method: 'foo' })

// () => number
const result2 = getMethod({ type: 'a', method: 'bar' })

// expected error
const result3 = getMethod({ type: 'a', method: 'z' })

操场

您可能已经注意到,我使用柯里化来推断dictionary.

在您的情况下,TypeScript 无法推断dictiopnary,因为它被声明在函数范围之外。

您可以在我的博客中找到更多函数参数推断示例

更新

您也可以使用这种方法:

const withConfig = <Dictionary,>(config: Dictionary) =>
    <Type extends keyof Dictionary,
        Method extends keyof (Dictionary)[Type]
    >(
        type: Type,
        method: Method
    ) => config[type][method]

const applyConfig = withConfig({
    a: {
        foo: () => 'foo',
        bar: () => 42
    },
    b: {
        baz: () => 'baz'
    }
})

applyConfig('a', 'bar') // ok
applyConfig('a', 'baz') // expected error

操场


推荐阅读