typescript 窄SomeType vs SomeType[]

7cjasjjr  于 2023-10-22  发布在  TypeScript
关注(0)|答案(2)|浏览(104)

我可以很容易地使用一个常量字符串值来缩小联合类型:

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是单个项或数组,会怎么样呢?现在有四种可能性:
  3. value.type"type1"value.payload * 不是 * array
  4. value.type"type1"value.payload * 是array
  5. value.type"type2"value.payload * 不是 * array
  6. value.type"type2"value.payload * 是array
    下面是一个示例:
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??
    }

};

操场

为什么typescript只是部分缩小了类型,我如何才能实现4种情况下的完全缩小值?

编辑:看起来if中的多个条件是无关紧要的; typescript仅基于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`?!
    }

};
4uqofj5v

4uqofj5v1#

您试图将T视为判别式并集,但payload属性未被识别为判别式。要将属性视为有效的判别式,它必须包含单位/文字类型。您的type属性有效,因为"type1""type2"是字符串文字类型。但是数组和Payload类型是对象类型,而不是文字类型。所以你不能检查value.payload并让它缩小value本身的明显类型。
请注意,Array.isArray(value.payload)确实充当value.payload属性的类型保护,但由于该属性不是判别式,因此这种收缩不会传播到value本身。在microsoft/TypeScript#42384上有一个开放的特性请求,允许属性类型保护传播到包含对象。不过,它还不是语言的一部分,之前对它的请求被拒绝了,因为它被认为对嵌套属性的每个类型保护检查合成新类型太昂贵了。
现在,如果你想获得这样的行为,你可以编写一个自定义的类型保护函数,根据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链接到代码

bvjveswy

bvjveswy2#

不可能一次缩小多个步骤的类型。
相反,您可以使用类型 predicate 或类型保护
例如:

type Payload1 = { action: string };
type Payload2 = { action: number };
type T1 = { type: "type1"; payload: Payload1 };
type T2 = { type: "type2"; payload: Payload2 };
type T3 = { type: "type1"; payload: Payload1[] };
type T4 = { type: "type2"; payload: Payload2[] };
type T = T1 | T2 | T3 | T4;

const isType1 = (arg: T1 | T2 | T3 | T4): arg is T1 | T3 => {
  return arg.type === "type1";
};
const isTypeArray = <T>(arg: T | T[]): arg is T[] => {
  return Array.isArray(arg);
};

下面是上面例子的codesandbox链接:playground

相关问题