先记住这个答案
TypeScript 对直接交给已知对象类型的新鲜对象字面量执行额外属性检查,用来发现拼错的配置键。先把对象保存为一个推断类型的变量,再赋给目标,通常按结构兼容判断:只要必需成员满足,多出的成员不一定被拒绝。这并非变量具有绕过所有检查的特权,缺少必需属性、成员类型错误和某些弱类型场景仍会失败。配置作者通常应修正字段或使用 satisfies 在定义处检查,而不是通过变量或断言掩盖拼写问题。
- 新鲜字面量会检查目标未声明的键
- 保存为变量通常改按结构兼容判断
- satisfies 可在定义处检查配置而保留推断结果
直接传配置为何更严格
配置对象常常写一次就传给库函数,未知属性很可能是拼写错误。若只按结构判断,一个可选配置键写错后被静默忽略,程序可能长期使用默认行为。额外属性检查把这类问题提前到调用位置,改善对象字面量的开发体验。
示例的配置要求路径。字面量中误写的 cahce 会报错,而变量拥有正确路径后可以按结构传入。变量仍带着那个拼错字段,库函数也不会因此认识它;两种写法编译结果不同,不能说明后者的业务行为就是正确的。
type RequestOptions = { path: string; cache?: boolean };
function send(options: RequestOptions) { return options.path; }
// @ts-expect-error cahce 不是配置字段
send({ path: '/items', cahce: true });
const stored = { path: '/items', cahce: true };
send(stored);
const checked = { path: '/items', cache: true } satisfies RequestOptions;
// @ts-expect-error satisfies 同样检查这里的未知字段
const typo = { path: '/items', cahce: true } satisfies RequestOptions;直接传入和 satisfies 声明中的错拼都应被检查出来,变量传入则满足路径契约。正确配置继续保留自身推断,不能把这种检查理解成运行时请求校验。
为什么不应把变量当作修复方案
如果未知字段本来就应该受到支持,应修正目标类型或提供明确扩展字段;如果只是拼写错误,应改键名。先存变量只改变编译器的检查上下文,不能使一个库开始消费陌生配置,因此常常只是把可诊断的问题隐藏起来。
索引签名可以允许额外键,但也会改变整个接口的约束。如果配置只接受少数确定选项,为消除一个报错就添加任意字符串索引,会削弱后续拼写检查。更清楚的扩展方案是单独的 metadata 字段,给自定义数据规定值类型和用途。
还有哪些情况不会因变量而通过
源变量若缺少必需字段,或同名字段是错误类型,结构兼容仍然失败。目标所有字段都可选时还可能触发弱类型检查:源与目标没有任何共同属性,不能简单认为空的最低要求就允许所有对象。诊断类型需要与实际代码一起看。
外部 JSON 不会因为标注了目标接口就被删除未知键。若接口禁止额外字段,或者需要避免把内部信息传出去,应在解析层验证键集合并构造白名单对象。静态开发体验与运行时协议验证解决不同阶段的问题,最好分别测试。
容易答错的地方
- 用 as 断言就是修复多余属性
- 断言表达调用者承担类型判断,并不会修正字段名或赋予库新的能力;应先确认类型定义是否确实漏写了合法配置。
- 接口只声明两个键就只能收到两个键
- 对象变量可能合法包含更多字段,接口描述的是可使用的静态契约;需要精确输出时必须执行实际的字段选择或验证。
面试官还会怎么问?
satisfies 会把值转换成目标类型吗?
它执行静态可赋值性检查,不生成值转换代码;表达式仍保留自身类型信息,但上下文类型可能影响字面量推断,不能理解为完全没有推断影响。
对象展开得到的额外键都会报错吗?
不能依赖它检查展开来源中的所有未知键;额外属性诊断与新鲜字面量的显式属性有关,精确键约束需要专门设计并验证。
组件属性也可以沿用这个判断吗?
可以借助相同思路理解结构与拼写检查,但 JSX、框架属性推断和泛型包装还会加入自己的上下文,应该查看最终参数类型。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。