Skip to content
On this page

几个迷惑的关键字

any 和 unknown 区别

主要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前,我们不必进行任何检查。总之, any 和 unknown 都是顶级类型,但是 unknown 更加严格,不像 any 那样不做类型检查,反而 unknown 因为未知性质,不允许访问属性,不允许赋值给其他有明确类型的变量。

  • 联合类型中的 unkown:
ts
type UnionType1 = unknown | null;       // unknown
type UnionType2 = unknown | undefined;  // unknown
type UnionType3 = unknown | string;     // unknown
type UnionType4 = unknown | number[];   // unknown

在联合类型中,unknown 类型会吸收任何类型。这就意味着如果任一组成类型是 unknown,联合类型也会相当于 unknown 。

ts
type UnionType5 = unknown | any;  // any

意外是 any 类型,如果至少一种组成类型是 any,联合类型会相当于 any 。

  • 交叉类型中的 unkown :
ts
type IntersectionType1 = unknown & null;       // null
type IntersectionType2 = unknown & undefined;  // undefined
type IntersectionType3 = unknown & string;     // string
type IntersectionType4 = unknown & number[];   // number[]
type IntersectionType5 = unknown & any;        // any

由于每种类型都可以赋值给 unknown 类型,所以在交叉类型中包含 unknown 不会改变结果。我们将只剩下 string 类型。

问题:如何将 unknown 类型指定为一个更具体的类型?

  • 使用 typeof 进行类型判断(这些缩小类型范围的技术都有助于TS基于控制流程下的类型分析)
ts
function unknownToString(value: unknown): string {
	if (typeof value === "string") {
		 return value;
	}
	return String(value);
}
  • 对 unknown 类型使用类型断言
    要强制编译器信任类型为 unknown 的值为给定类型,则可以使用类型断言:
ts
const value: unknown = "Hello World";
const foo: string = value; // Error
const bar: string = value as string; // OK

断言错了时语法能通过检测,但是运行的时候就会报错了!

ts
const value: unknown = "Hello World";
const bar: number = value as number; // runtime Error

关于 any 和 unknown 的区别,看这篇文章。 any 和 unknown 傻傻分不清楚? - 掘金 (juejin.cn) 个人理解 unknown :它是所有类型的并集,如果使用一个 unknown 变量且被赋值,需要对该变量进行类型收窄操作。

never 和 void

在TS中,有一个很特殊的类型,就是 never,never 只能在两种情况下使用。

  • 函数永远不会有返回值时,例如 while(true){}
typescript
const add: ()=>never = () => {
  while(true){ i++ }
}
  • 函数永远会抛出一个错误时。
typescript
const fn: ()=>never = () => {
  throw new Error('error')
}
  • 与void的差异 void 和 never 都是表示一个函数没有返回值,但是他们之间最大的区别是,void 表示可以被赋值的类型never 表示其他任何类型也不能被赋值给它,它只能是 never 。

type 和 interface

两者区别:interface 只描述对象,type 则描述所有数据;type 只是别名,interface 则是类型声明;type 不可重新赋值,interface 会自动合并(可用于扩展某类型);对外暴露 api 尽量用 interface,方便扩展,对内 api 尽量用 type ,防止代码分散。

ts
declare global {
	interface String {
		padZero(length: number): string;
	}
}

infer 关键字

用于推断泛型参数,显示地声明泛型变量。

ts
type getIntersection<T> = T extends (a: infer P,b: infer P) => void ? P : never;
type Intersection = getIntersection<(a: string, b: number)=> void> // string & number
  1. infer 必须在 extends 右侧使用,因为必须保证这个已知类型是由右侧的泛型推出来的,不然推导它的参数还有什么意义呢?检查时会跳过使用了 infer 的地方。
  2. infer 可以在 extends 的条件语句中推断待推断的类型。
ts
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type func = () => number;
type variable = string;
type funcReturnType = ReturnType<func>; // funcReturnType 类型为 number
type varReturnType = ReturnType<variable>; // varReturnType 类型为 any
  1. infer的作用不止是推断返回值,还可以解包。
ts
type Ids = number[]; 
type Names = string[];
type Unpacked<T> = T extends (infer R)[] ? R : T;

type idType = Unpacked<Ids>; // idType 类型为 number
type nameType = Unpacked<Names>; // nameType 类型为string

type Response = Promise<number[]>;
type Unpacked<T> = T extends Promise<infer R> ? R : T;
type resType = Unpacked<Response>; // resType 类型为number[]
  1. 推断联合类型,同一个类型变量在推断的值有多种情况的时候会推断为联合类型,针对这个特性,很方便的可以将元组转为联合类型。
ts
type ElementOf<T> = T extends (infer R)[] ? R : never;

type TTuple = [string, number];
type Union = ElementOf<TTuple>; // Union 类型为 string | number

Released under the MIT License.