我可以轻松地使用常量字符串值来缩小联合类型的范围:

type Payload1 = { /* ... arbitrary type ... */ };
type Payload2 = { /* ... arbitrary type ... */ };
type T1 = { type: 'type1', payload: Payload1 }
type T2 = { type: 'type2', payload: Payload2 }
type T = T1 | T2;

const fn = (value: T) => {

    if (value.type === 'type1') {
        value; // Typescript knows `value is T1`
    }

    if (value.type === 'type2') {
        value; // Typescript knows `value is T2`
    }

};

这里只有两种情况:

  1. value.type是常量"type1"
  2. value.type是常量"type2"

但是,如果我扩展T,允许payload既可以是单个项,也可以是一个数组,会怎么样呢?现在有4种可能性:

  1. value.type"type1",value.payloadnotarray
  2. value.type"type1",value.payload isarray
  3. value.type"type2",value.payloadnotarray
  4. value.type"type2",value.payload isarray

下面是一个例子:

type Payload1 = {};
type Payload2 = {};
type T1Single = { type: 'type1', payload: Payload1 }
type T1Batch = { type: 'type1', payload: Payload1[] };
type T2Single = { type: 'type2', payload: Payload2 }
type T2Batch = { type: 'type2', payload: Payload2[] };

// Here's T, now with 4 types instead of 2:
type T = T1Single | T1Batch | T2Single | T2Batch;

const fn = (value: T) => {

    if (value.type === 'type1' && !Array.isArray(value.payload)) {
        value; // Typescript says `value is T1Single | T1Batch`?!
        // How does `T1Batch` remain in the union if `value.payload` isn't an array??
    }

    if (value.type === 'type1' && Array.isArray(value.payload)) {
        value; // Typescript says `value is T1Single | T1Batch`?!
        // How does `T1Single` remain in the union if `value.payload` is an array??
    }

    if (value.type === 'type2' && !Array.isArray(value.payload)) {
        value; // Typescript says `value is T2Single | T2Batch`?!
        // How does `T2Batch` remain in the union if `value.payload` isn't an array??
    }

    if (value.type === 'type2' && Array.isArray(value.payload)) {
        value; // Typescript says `value is T2Single | T2Batch`?!
        // How does `T2Single` remain in the union if `value.payload` is an array??
    }

};

Playground

Why is typescript only partially narrowing down the type, and how can I achieve fully narrowed values for the 4 cases?

编辑:看起来if个条件中的多个条件无关紧要;仅凭Array.isArray个字就很难缩小范围:

type Payload = {};
type Single = { payload: Payload }
type Batch = { payload: Payload[] };

const fn = (value: Single | Batch) => {

    if (!Array.isArray(value.payload)) {
        value; // Typescript says `value is Single | Batch`?!
    }

    if (Array.isArray(value.payload)) {
        value; // Typescript says `value is Single | Batch`?!
    }

};

推荐答案

您试图将T视为discriminated union,但payload属性未被识别为判别式.要将属性视为有效的判别式,它必须包含单位/literal types.您的type属性是有效的,因为"type1""type2"是字符串文字类型.但是数组和您的Payload类型是对象类型,而不是文本类型.因此,你不能判断value.payload,并让它缩小value本身的明显类型.

请注意,Array.isArray(value.payload)确实充当value.payload属性的类型保护,但因为该属性不是判别式,所以这种限制不会传播到value本身.在microsoft/TypeScript#42384处存在开放特征请求,以允许属性类型保护传播到包含对象.不过,它还不是该语言的一部分,以前对它的请求被拒绝了,因为为嵌套属性上的每个类型保护判断合成新类型的成本太高了.


目前,如果您想获得这样的行为,可以编写一个custom type guard function,根据其payload属性是否为数组来缩小一个值.就像这样:

function hasArrayPayload<T extends { payload: any }>(
    value: T): value is Extract<T, { payload: any[] }> {
    return Array.isArray(value.payload)
}

然后,您可以调用hasArrayPayload(value),而不是内联编写Array.isArray(value.payload):

const fn = (value: T) => {
    if (value.type === 'type1' && !hasArrayPayload(value)) {
        value; // (parameter) value: T1Single
    }

    if (value.type === 'type1' && hasArrayPayload(value)) {
        value; // (parameter) value: T1Batch
    }

    if (value.type === 'type2' && !hasArrayPayload(value)) {
        value; // (parameter) value: T2Single
    }

    if (value.type === 'type2' && hasArrayPayload(value)) {
        value; // (parameter) value: T2Batch
    }
};

Playground link to code

Typescript相关问答推荐

如何基于对象关键字派生类型?

如何使打包和拆包功能通用?

为什么在TypScript中写入typeof someArray[number]有效?

带有联合参数的静态大小数组

参数类型undefined不能分配给参数类型字符串|未定义

如何在方法中定义TypeScript返回类型,以根据参数化类型推断变量的存在?

HttpClient中的文本响应类型选项使用什么编码?''

TypeScrip表示该类型不可赋值

为什么在回调函数的参数中不使用类型保护?

有没有可能为这样的函数写一个类型?

已解决:如何使用值类型限制泛型键?

笛卡尔产品类型

将具有Readonly数组属性的对象接口转换为数组

如何正确调用创建的类型?

在抽象类属性定义中扩展泛型类型

有没有什么方法可以使用ProvideState()提供多个削减器作为根减减器

TypeScript:方法获胜';t接受较窄类型作为参数

如何限定索引值必须与相应属性的id字段相同

如何在TypeScript中声明逆变函数成员?

TS 排除带点符号的键