打字稿歧视联合缩小不起作用

Typescript discriminated union narrowing not working

我有以下(简化的)代码,我在其中尝试使用可区分的联合类型。当我尝试访问 data 时,TS 认为它可能是 undefined,我不确定为什么。

type Return =
  | {
    isReady: 'yes';
    data: { a: number };
  }
  | {
    isReady: 'no';
    data: false;
  };

const myFunction = (): Return => {
  return {
    isReady: 'yes',
    data: { a: 111 }
  };
}

const { isReady, data } = myFunction()

if (isReady === 'yes') {
  const { a } = data; // ERROR: Property 'a' does not exist on type '{ a: number; } | undefined'.
}

您可以尝试检查 data 类型:

type Return =
  {
    isReady: 'yes';
    data: { a: number };
  }
  | {
    isReady: 'no';
    data: false;
  };

const myFunction = (): Return => {
  return {
    isReady: 'yes',
    data: { a: 111 }
  };
}

const { isReady, data } = myFunction()

if (typeof data !== 'boolean') {
  const { a } = data;
}

Playground Example

通过解构 myFunction() 的已区分联合结果,您不幸地破坏了编译器在 isReadydata 属性之间可以看到的任何相关性:

const { isReady, data } = myFunction()
// const isReady: "yes" | "no"
// const data: false | {  a: number; }

这里,编译器认为isReadydata都是union types,每个都有两个成员。那不是 错误的 ,但是编译器现在只看到 isReadydata 这对本质上有 四个 可能的成员:

const oops: (
    | { isReady: "yes", data: false }
    | { isReady: "no", data: false }
    | { isReady: "yes", data: { a: number } }
    | { isReady: "no", data: { a: number } }
) = { isReady, data }

因此检查 isReady 对编译器可以跟踪的 data 没有影响。


TypeScript 对我一直称之为相关联合类型 的支持的限制是功能请求microsoft/TypeScript#30581. TypeScript 4.4 has introduced support for aliased conditions (as implemented in microsoft/TypeScript#44730) 的主题,您可以在其中存储类似isReady 放入变量中,编译器稍后将使用它来区分您的联合:

const result = myFunction();
const isReady = result.isReady;

if (isReady === 'yes') {
    const { a } = result.data; // okay in TypeScript 4.4
}

但是as noted here,

the pattern of destructuring a discriminant property and a payload property into two local variables and expecting a coupling between the two is not supported as the control flow analyzer doesn't "see" the connection. For example:

type Data = { kind: 'str', payload: string } | { kind: 'num', payload: number };

function foo({ kind, payload }: Data) {
    if (kind === 'str') {
        payload.length;  // Error, payload not narrowed to string
    }
}

We may be able to support that pattern later, but likely not in this PR.

所以目前还没有办法做到这一点。


目前最可行的方法就是根本不解构,而是按照它们本来的用途来使用可区分的联合,作为具有判别式的单个对象 属性:

const result = myFunction();
if (result.isReady === 'yes') {
    const { a } = result.data; // okay
}

我会在 microsoft/TypeScript#30581 将其添加到不断增加的问题列表中。

Playground link to code