读第二个类型,而不只是第一个
TS2345 同时列出赋值两侧的类型。多数开发者只读第一个类型,认出自己的值就停下。有用信息在目标类型里:哪个属性是必需的、允许哪个字面量、参数是否接受联合类型。编译器在第一个不兼容处停止,所以先修这个属性再重跑,不要猜其余部分。
type Status = 'draft' | 'published';
function setStatus(status: Status) {}
const value = 'draft'; // 推断为 string,而不是 'draft'
setStatus(value); // TS2345
变量被推断为 string,因为 let 和对象属性会拓宽字面量。const value = 'draft' as const 或显式标注 Status 可以保留字面量。对对象字面量,satisfies 会检查形状而不拓宽值:
const config = { status: 'draft' } satisfies { status: Status };
setStatus(config.status); // OK
调用前先收窄
参数是联合类型时,运行时检查同时也是编译期收窄:
type Result = { ok: true; value: string } | { ok: false; error: Error };
function handle(result: Result) {
if (result.ok) {
consume(result.value); // 收窄到成功分支
}
}
result as { value: string } 这样的断言能编译,但不会在运行时检查判别字段。优先使用守卫或可辨识联合。如果数据来自 JSON.parse,断言尤其危险:解析结果是 any,编译器无法验证。
契约错误时修签名
有时调用点正确,而参数过窄。如果函数接受 string[],但调用方传只读数组,就改成 readonly string[]。如果某个可选字段在某个流程中总会提供,拆分函数或使用重载。不要为了消错给参数加 any,那会移除所有调用方的检查。
在相同配置下验证
编辑器诊断可能与 CI 不同,如果它们使用不同的 tsconfig 或 TypeScript 版本。运行 CI 使用的同一条 tsc --noEmit,并确认修复没有通过 skipLibCheck 或宽泛 exclude 压制错误。不同配置下的本地干净构建不是证据。