网站搜索

Typescript 控制流分析无法识别已知类型


我正在尝试理解为什么即使 Policies 类型中的值只能是我的 Types 之一,编译器仍然需要明确告诉它值的类型。

type Types = 'baz' | 'bar';

// tagged union types
type Foo<T extends Types> = { type: T; }
type FooOne = { one: string } & Foo<'baz'>;
type FooAnother = { another: string } & Foo<'bar'>;

type Predicate<T extends Types> = (e: Foo<T>) => boolean;

type Policies = {
    [P in Types]: Predicate<P>
}

const policies: Policies = {
    baz: (e: FooOne) => e.one === 'neo',
    bar: (e: FooAnother) => e.another === 'morphieus'
}

// this method receives a union type
function verify(e: FooOne | FooAnother) {
    // these both work, understandably
    if (e.type === 'baz') {
        const policy1 = policies[e.type]; // ide says this type is a Predicate<'baz'>
        const result1 = policy1(e); // fine  
    } else {
        const policy2 = policies[e.type]; // and this is a Predicate<'bar'>
        const result2 = policy2(e); // fine
    }

    // but this doesn't work even though e.type is known to be 'baz' | 'bar', and the keys at policies can only be 'baz' | 'bar'
    const policy3 = policies[e.type]; // ide says this type is a Predicate<'bar'> | Predicate<'baz'>
    const result3 = policy3(e); // error Cannot invoke an expression whose type lacks a call signature  

    // this works, if i hint to the compiler what is at policies[T]
    const policy4: Predicate<Types> = policies[e.type]; // Predicate<'baz' | bar'>
    const result4 = policy4(e); // fine 
}

答案#1

这里没有好的解决方案,你需要某种类型断言来让事情正常运作。

问题在于,流分析无法跟踪值的来源。因此当你写道:

const policy3 = policies[e.type]; 
const result3 = policy3(e);

policy3 将会是 Predicate<"baz"> | Predicate<"bar">,但编译器不知道具体是哪一个。当你调用 policy3 时,由于编译器不知道变量中实际是哪种函数类型,它将在较新版本中强制你指定一个可以与联合中的任意函数兼容的参数。它不会记住 policy3 实际上与 e.type 相关联,并且 policy3(e) 是类型安全的。(供参考:在旧版本中,policy3 根本无法调用,因为它是一个联合类型)

这段代码可以运行:

const policy4= policies[e.type] as Predicate<Types> ; // Predicate<'baz' | bar'>
const result4 = policy4(e); // fine 

但是 Predicate<"baz"> | Predicate<"bar"> 并不等同于 Predicate<Types>。前者是一个接受 baz 的函数或者一个接受 bar 的函数,而后者是一个接受 baz 或 bar 的函数。因此调用可以工作,因为你已经断言函数可以接受任意一种参数类型(这并不完全正确,但就像我说的,没有好的解决方法)。