2017-09-05 99 views
3

似乎有時即使存在類型警衛,TypeScript(最近版本)仍未能縮小聯合類型。 這種行爲是錯誤或功能:TypeScript聯合類型推斷失敗

序言:

// some config 
interface Config { 
    name: string; 
    option1?: number; 
    option2?: boolean; 
} 

// arbitrary type 
interface Entity { 
    a: number; 
    b: number; 
} 

// name aware type guard for entity property-to-config map 
// some default config may be replaced with a property name 
type TConfigSet<TData> = { 
    [P in keyof TData]: (Config & { name: P }) | P; 
} 

// example of TConfigSet usage 
const EntityConfigs: TConfigSet<Entity> = { 
    a: { 
     name: 'a', 
     option2: true 
    }, 
    b: 'b' 
} 

問:

// this function compiles 
function TypeLooseFieldToName(name: string | Config): string { 
    if (typeof name === 'string') return name; 
    else return name.name; 
} 

// this one doesn't 
function TypeStrictFieldToName<TData>(name: keyof TData | { name: keyof TData }): keyof TData { 
    if (typeof name === 'string') return name; 
    else return name.name; // still keyof TData | { name: keyof TData }, should be shrinked to { name: keyof TData } 
} 

回答

1

這似乎是在類型檢查的錯誤,因爲打字稿手冊說

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

作爲一種變通方法,類型後衛可以反轉,以排除自定義類型第一:

function hasName(obj: string | { name: string }): obj is { name: string } { 
    return typeof obj.name === 'string'; 
} 

function getName<TData>(name: keyof TData | { name: keyof TData }): keyof TData { 
    if (hasName(name)) return name.name; 
    else return name; 
} 

// compiles with valid keys 
getName<Entity>('a'); 
getName<Entity>({ name: 'a' }); 

// doesn't compile with invalid keys 
getName<Entity>('z'); 
getName<Entity>({ name: 'z' }); 

你可以搜索打字稿問題在GitHub上,如果這不是招提交新問題「T以前解決:

https://github.com/Microsoft/TypeScript/issues

+0

你是正確的,這是固定在2.6中的錯誤 –

0

typeof name === 'string'不作爲型後衛,因爲顯然stringkeyof TData被認爲是工作了keyof TData編譯器提供了兩種不同的類型。如果您添加自己的自定義類型的後衛keyof TData它的工作原理:「一個keyof T類型被認爲是string亞型」

function TypeStrictFieldToName<TData>(name: keyof TData | { name: keyof TData }): keyof TData { 
    if (isKeyofTData<TData>(name)) return name; 
    else return name.name; // type of name here is { name: keyof TData } 
} 

function isKeyofTData<TData>(name: keyof TData | {}): name is keyof TData { 
    return typeof name === 'string'; 
}