网站搜索

TypeScript - 基于其他成员类型的条件成员类型


是否可以根据特定条件指定多种可能的成员类型?例如,给出以下类型:

interface DataItem {
    value: boolean | Date | string | string[];
    options: undefined | string[];
}

我想说明,如果:

  • value 的类型是 boolean 或 Date 时,options 必须 是 undefined 类型。
  • value 的类型是 string 或 string[],那么 options 必须 是 string[] 类型。

在当前版本的 TypeScript (2.2) 中,有没有办法指定上述约束?另外,如果有的话,我假设对一个成员添加类型保护应该会应用到其他成员,即:

let data: DataItem;
if (typeof data.value === 'boolean') {
    data.options = ['a']; // => Error Type 'string[]' is not assignable to type 'undefined'.
}

我尝试了以下方法,但没有奏效:

interface BooleanOrDateItem {
    value: boolean | Date;
}

interface StringOrStringArrayDataItem {
    value: string | string[];
    options: string[];
}

type DataItem = BooleanOrDateItem | StringOrStringArrayDataItem;

let data: DataItem;

if (typeof data.value === 'string') {
    // I would expect this to work but the compiler complains 
    // => Property 'options' does not exist on type 'DataItem'.
    //          Property 'options' does not exist on type 'BooleanOrDateItem'.
    data.options = ['a'];
}

显式转换为 StringOrStringArray 显然是可行的

if (typeof data.value === 'string') {
    (<StringOrStringArrayDataItem>data).options = ['a'];
}

但是编译器不应该代表我做这件事吗,因为它有足够的信息来推断 data 的类型是 StringOrStringArrayDataItem?

答案#1

我相信你正在寻找用户定义的类型保护。在每个if语句之后,如果返回值为true,TypeScript 会将dataItem的类型缩小为正确的类型。

interface DataItem {
    value: boolean | Date | string | string[];
    options: undefined | string[];
}

interface BoolOrDateDataItem extends DataItem {
    value: boolean | Date;
    options: undefined;
}
interface StringDataItem extends DataItem {
    value: string | string[];
    options: string[];
}

function isBoolOrDateDataItem(dataItem: DataItem): dataItem is BoolOrDateDataItem {
    return typeof dataItem.value === 'boolean' || dataItem.value instanceof Date;
}

function isStringDataItem(dataItem: DataItem): dataItem is StringDataItem {
    return typeof dataItem.value === 'string' || dataItem.value instanceof Array;
}

let dataItem: DataItem = {
    value: true,
    options: undefined
} // Or whatever it may be

if (isStringDataItem(dataItem)) {
    dataItem.value = new Date(); // TypeScript warns value must be string | string[] - knows it is a StringDataItem here
} else if (isBoolOrDateDataItem(dataItem)) {
    dataItem.value = true; // No error
}

这确实有点啰嗦,但似乎是推荐的做事方式。