网站搜索

typescript - 可选字段和联合类型有什么区别?


在……之间有实际差别吗:

{
    ok: boolean;
} | {
    ok: boolean;
    error: any;
}

和 :

{
    ok: boolean;
    error?: any;
}

我注意到前端方法返回值的推断类型有区别,这两个方法都做类似如下的事情:

try {
  const response = await axios.post(url)
  return response.data as Payloads[Endpoints.EndpointName]
} catch (error) {
  return { ok: false, error }
}

其中一个 Payloads[Endpoints.EndpointName] 是:

{
    ok: boolean,
    error?: any,
}

还有一个只是:

{
    ok: boolean,
}

我期望两个返回值的行为相同,但由于它们在形式上不相同,我想知道是否需要注意一些特性。我能想到的唯一区别是联合类型可以使用 Exclude(以及通常用于联合类型的辅助工具)。还有其他区别吗?

答案#1

TypeScript 不允许你使用一个键对对象类型进行索引,除非该键已知存在于该类型中。这在很大程度上导致了 {ok: boolean} | {ok: boolean, error: any} 与 {ok: boolean; error?: any} 之间的明显差异。

可选属性和必需属性之间通常还有进一步的区别,涉及如何处理 undefined,但由于你的属性类型是通用的 any 类型,这种区别基本上消失了。


类型 {ok: boolean} 并不知道有 error 属性,因此如果你尝试用 error 键去索引它,编译器会报错。这并不意味着类型为 {ok: boolean} 的值不能 拥有 一个 error 属性;TypeScript 中的对象类型不是封闭的(更多信息请参见 TypeScript: prevent assignment of object with more properties than is specified in target interface)。因此,单独的类型 {ok: boolean} 已经类似于 {ok: boolean; error?: any}。但是,用一个不确定存在的键去索引对象类型被认为是一个错误。

此外,如果你有一个对象类型的联合类型,你只能使用在联合类型的每个成员中都存在的键来进行索引。因此,{ok: boolean} | {ok: boolean; error: any} 仍然不允许你使用 error 键进行索引,因为第一个联合成员并不知道 error:

type T = { ok: boolean; } | { ok: boolean; error: any; }
declare const c: T;
c.error = {} // error  

另一方面,{ok: boolean; error?: any} 确实知道 error 属性,因此你可以对它进行索引。

type T = { ok: boolean; error?: any; }
declare const c: T;
c.error = {} // okay

IntelliSense 只会建议已知存在的键,因此你也会看到建议键的差异。


此外,具有非联合属性的对象联合在概念上类似于具有联合属性的非联合对象,但在 TypeScript 中它们不被视为等效。你的示例类型不是完全如此,因为 any 会消除这种区别,但它很接近。比较 {a: string} | {a: number} 与 {a: string | number}。

主要的区别在于,联合类型可以通过各种类型保护直接缩小,而非联合对象通常不能通过这种方式缩小。

例如,如果你给联合类型的变量赋值,编译器会将该变量的表面类型缩小到仅相关的联合成员。根据你赋的值,访问该变量时可能会得到不同的后续行为:

// union behavior
type T = { ok: boolean; } | { ok: boolean; error: any; }
const a: T = { ok: true }
a.error // error
const b: T = { ok: true, error: {} }
b.error // okay

// non-union behavior
type T = { ok: boolean; error?: any; }
const a: T = { ok: true }
a.error // okay
const b: T = { ok: true, error: {} }
b.error // okay

因此,虽然 a 显示了联合类型和非联合类型之间的相同区别,但在联合情况中 b 被缩小为 {ok: boolean; error: any},因此它的行为几乎就像使用非联合类型一样(它们之间的唯一区别是一个中 error 是可选的,而另一个中是必需的,但这个区别作用不大)。

代码的 Playground 链接