如何把 unknown 指定为更具体的类型?
下文默认基于 TypeScript 5.x,并假设开启
strict: true。
面试速答(30 秒版 TL;DR)
unknown不能直接当成具体类型使用,必须先做类型收窄(type narrowing)。- 最常见的收窄方式有:
typeofinstanceofArray.isArrayin- 自定义类型守卫
value is Xxx - 必要时使用类型断言
as Xxx
- 如果数据来自外部系统,最稳的做法不是直接
as,而是先校验再收窄。
心智模型:先证明,再使用
1. 用 typeof 收窄基础类型
function print(value: unknown) {
if (typeof value === "string") {
console.log(value.toUpperCase());
}
if (typeof value === "number") {
console.log(value.toFixed(2));
}
}
适用范围:
stringnumberbooleanbigintsymbolundefinedfunction
注意:
typeof null === "object",不能靠它单独判断普通对象。
2. 用 instanceof 收窄类实例
function format(value: unknown) {
if (value instanceof Date) {
return value.toISOString();
}
if (value instanceof Error) {
return value.message;
}
return String(value);
}
适合:
DateError- 自定义 class 实例
不适合:
- 普通对象字面量结构判断
3. 用 Array.isArray 判断数组
function first(value: unknown) {
if (Array.isArray(value)) {
return value[0];
}
return undefined;
}
这是数组判断最稳的入口,不要用 typeof value === "object" 代替。
4. 用 in 判断对象上是否存在某个字段
function readName(value: unknown) {
if (
typeof value === "object" &&
value !== null &&
"name" in value
) {
return value.name;
}
return undefined;
}
这里必须先判断:
typeof value === "object"value !== null
否则 in 对 null 或原始值会出问题。
5. 用自定义类型守卫做可复用收窄
这是工程里最常用、也最值得面试重点说的方式。
type User = {
id: number;
name: string;
};
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
typeof value.id === "number" &&
"name" in value &&
typeof value.name === "string"
);
}
function greet(value: unknown) {
if (isUser(value)) {
return `hello, ${value.name}`;
}
return "invalid user";
}
关键点:
- 返回值写成
value is User; - 这不是运行时新类型,而是告诉编译器“如果返回
true,这里就按User看”。
6. 类型断言 as 可以用,但不是首选
const value: unknown = JSON.parse('{"name":"Tom"}');
const user = value as { name: string };
这叫类型断言,不叫验证。
也就是说:
- 编译器会相信你;
- 运行时不会帮你检查真假。
因此面试里最好这样表述:
as适合你已经非常确定数据来源;- 边界输入更推荐“校验 + 收窄”,而不是“盲断言”。
7. 实战建议:边界层做一次收窄,不要到处散着写
不推荐:
const data: unknown = await fetchSomething();
// 后面每个地方都在 as
更推荐:
type ApiUser = {
id: number;
name: string;
};
function assertApiUser(value: unknown): asserts value is ApiUser {
if (
typeof value !== "object" ||
value === null ||
!("id" in value) ||
typeof value.id !== "number" ||
!("name" in value) ||
typeof value.name !== "string"
) {
throw new Error("invalid api user");
}
}
然后在入口统一校验,后面业务代码就能稳定使用具体类型。
8. 高频面试追问
8.1 unknown 和 as 一起用是不是就够了
不够。
因为 as 只是告诉编译器你相信它,不是真校验。
8.2 收窄和断言有什么区别
- 收窄:通过代码分支让编译器推导出更具体类型;
- 断言:开发者直接指定“把它当成这个类型”。
收窄更安全,断言更冒险。
8.3 为什么很多库会引入 zod / valibot / io-ts
因为当对象结构很复杂时,手写守卫容易:
- 重复;
- 漏字段;
- 难维护。
这类库本质上是在做“运行时校验 + 静态类型联动”。
速记要点
unknown变具体类型的核心不是as,而是收窄。- 常用手段:
typeof、instanceof、Array.isArray、in、自定义守卫。 - 外部输入优先“校验后收窄”,不要直接盲断言。