示例说明
any 会关闭类型检查让脏数据长驱直入,unknown 才是外部数据的正确入口——它强制你先证明再使用。守卫函数的返回类型谓词让编译器在 true 分支把值视为目标形态;hasKeys 用泛型键列表同时完成存在性检查与键类型收窄,与 switch 判别分支叠加后每个分支内部都拿到精确类型。
技术要点
- 类型谓词让守卫结果驱动收窄
- 泛型键校验同时收窄键集合
- default 分支兜住新增判别值
代码解读
isRecord 排除 null 与数组;hasKeys 在其上确认必需键存在。parseOutcome 按 status 分支:成功分支验证 data 是字符串后返回窄化对象,失败分支验证两个再返回,default 把未识别的判别值显式拒绝——新增状态时不会静默漏处理。
使用建议
守卫链要与真实载荷形态对齐,JSON 数字与字符串的区分要显式处理。公共守卫抽成工具库复用。收窄后的值仍建议过 schema 校验做业务约束。
代码示例
interface Success<T> { status: 'success'; data: T }
interface Failure { status: 'failure'; code: number; message: string }
type Outcome<T> = Success<T> | Failure;
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function hasKeys<K extends string>(
value: unknown, ...keys: K[]
): value is Record<K, unknown> {
return isRecord(value) && keys.every((k) => k in value);
}
function parseOutcome(raw: unknown): Outcome<string> {
if (!hasKeys(raw, 'status')) {
throw new TypeError('outcome requires a status field');
}
switch (raw.status) {
case 'success':
if (typeof raw.data !== 'string') {
throw new TypeError('success outcome needs string data');
}
return { status: 'success', data: raw.data };
case 'failure':
if (!hasKeys(raw, 'code', 'message')) {
throw new TypeError('failure outcome needs code and message');
}
if (typeof raw.code !== 'number' || typeof raw.message !== 'string') {
throw new TypeError('failure fields have wrong types');
}
return { status: 'failure', code: raw.code, message: raw.message };
default:
throw new TypeError('unknown status: ' + String(raw.status));
}
}
const ok = parseOutcome(JSON.parse('{"status":"success","data":"hello"}'));
console.log(ok.status === 'success' ? ok.data.toUpperCase() : 'n/a');