先记住这个答案
嵌套联合通常先检查外层标签,取得该状态下存在的内层数据,再检查内层自己的标签。这样每一步都使用已经建立的字段关系。若把区分外层成员的唯一标签藏在嵌套对象里,TypeScript 不一定能根据内层字段比较反推整个父对象的专有字段;可将判别字段放到需要区分的那层,或编写与完整结构一致的守卫。解构时也应保留标签与负载的关联,尤其不要假定标签与 rest 对象拆开后仍有同等收窄能力。
- 先证明外层状态,再读取内层状态与负载
- 内层标签不保证自动反推所有父对象成员
- 标签和相关负载应尽量保持在同一层关联中
先确认数据存在,再判断内容类别
请求未完成时并没有结果内容,因此不能直接检查内容标签。外层状态负责区分加载与就绪,就绪成员再携带文本或图像联合。这样既不会允许加载状态假装拥有结果,也不必让所有层级字段都变成可选。
示例先处理加载状态,后续路径自然获得内容,再根据内部 kind 选择文本或地址。将已经存在的内层对象保存为局部常量,可以让接下来的标签检查与负载读取保持在同一个对象上,减少复杂路径造成的阅读负担。
type Content =
| { kind: 'text'; text: string }
| { kind: 'image'; src: string };
type View =
| { state: 'loading' }
| { state: 'ready'; content: Content };
function describeView(view: View): string {
if (view.state === 'loading') return 'loading';
const content = view.content;
if (content.kind === 'text') return content.text;
return content.src;
}外层检查先证明 content 存在,内层检查再决定能读取 text 还是 src。每层只承担自己的候选区分,没有通过可选字段或强制断言跳过中间证据。
为什么检查嵌套标签不一定收窄父对象
另一个模型可能让两个父成员都拥有 meta,但 meta.kind 分别不同,同时父层携带各自专有数据。检查 meta.kind 能细化被读取的嵌套路径,却不一定让父对象整体变成某个成员。类型系统并不提供任意深度关系反推的通用保证。
如果消费端总要根据标签使用父层专有字段,把标签提升到父层通常更清楚。确有固定外部协议无法调整时,可以在解析后映射内部结构,或编写正确的父类型守卫。不要为每次读取都添加断言,否则模型与使用方式的错位会持续扩散。
解构和状态组合的维护方式
现代 TypeScript 支持部分稳定解构的判别关联,但把标签与剩余属性通过 rest 分开、之后再修改绑定或跨异步阶段使用,可能失去预期关系。遇到报错可先保留完整对象,在分支内解构对应负载,通常更容易表达和检查。
并不是所有状态维度都应该嵌套成庞大枚举。彼此独立的筛选条件可以独立建模,存在严格依赖的数据才需要绑定到对应成员。增加状态时,应分别审查外层和内层处理是否齐全,并用典型组合验证没有允许无意义或遗漏合法的状态。
容易答错的地方
- 检查最深层 kind 就能推断所有祖先字段
- 嵌套路径证据不等于任意父对象的完整成员选择;应让判别字段与需要关联的负载位于合适层级,或提供准确的结构守卫。
- 解构 rest 只是换名字,不影响类型关系
- 标签与剩余对象分离后可能不再保留判别关联,消费端应在收窄完整成员后解构,或使用编译器实际支持的稳定绑定形式。
面试官还会怎么问?
所有外部嵌套协议都需要改成平铺吗?
不必修改服务端协议,可以在边界解析后转换为方便内部使用的状态结构;如果原结构已经清楚表达依赖,逐层收窄就足够。
保存 content 局部常量是否意味着内容冻结?
不是,它只是稳定这次引用绑定,内部对象仍可能可变;若使用过程中可能被其他代码修改,还要明确所有权或获取合适快照。
外层和内层都要做穷尽检查吗?
只要各层要求处理所有有限成员,就可以分别加入检查;新增内容类别与新增请求状态影响的处理点不同,应按各自契约验证。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。