基本类型

类型兼容性

A\Bbooleannumberstringsymbolundefinednullvoidneverunknownany
boolean×××××××
number×××××××
string×××××××
symbol×××××××
undefined××××××
null×××××××
void×××××××
never
unknown××××××××
any×

第一列表示 A,第一行表示 B。表格里展示的是 A 是否能赋值给 B。

void/never

void类型表示没有return语句的函数的返回类型。

never类型表示永不返回任何值的函数的返回类型。

这些类型都不代表运行时的任何值,它们只是作为 TypeScript 类型系统的辅助检查手段。

// type Collection = any
type Collection = void | never | any;
// type Collection = unknown
type Collection = void | never | unknown;
1
2
3
4

any/unknown

any

绝大多数情况下,你不需要为实体(entity)提供类型,因为 TypeScript 编译器可以根据实体的初始值(比如函数参数的默认值、函数的返回值、对象属性的初始值)推断出来。

当 TypeScript 无法确定变量的类型时,它将隐式地设置变量的类型为any。比如let x;表达式声明了一个变量x,但是我们没有为其提供类型也没有设置初始值,因此x的类型被设置为any

any类型仅存在于 TypeScript 里,它是一个集合类型(collective type),包含了所有存在于 JavaScript 运行时的值的类型。这意味着,stringnumbersymbolnullundefined以及其他所有存在于 JavaScript 的值也是any类型。因此,any类型有时也称为顶级类型top type)或超类型supertype)。

因为any是所有 JavaScript 值的超类型,一个包含了any的联合类型将收窄为any类型。由于交叉类型通过结合多个类型的形状生成一个类型,一个包含了any的交叉类型将返回any类型。

// type Collection = any
type Collection = string | number | undefined | any;
// type Collection = any
type Collection = string & any;
1
2
3
4

这意味着你可以声明一个any类型的变量,并且可以将任何 JavaScript 值赋给x。更近一步,你还可以将any类型的值赋给一个已知类型的变量。

let x: any;
x = 1;
x = 'one';
x = true;

let y: boolean = x;
1
2
3
4
5
6

注意,这可能会在运行时导致大问题。

由于any类型没有确定的形状,你将无法从 IDE 上获得任何关于自动补全和智能提示的帮助。但是,你可以使用类型断言语法比如x as Person,将any类型的x断言为Person类型。

我们也可以使用类型守卫将any类型收窄。由于any类型表示许多类型的集合,就像一个联合类型,TypeScript 可以使用类型守卫来鉴别一个类型。

class Student {
    constructor(
        public name: string, public marks: number
    ){ }

    getGrade(): string {
        return (this.marks / 10).toFixed( 0 );
    }
}

class Player {
    constructor(
        public name: string, public score: number
    ){}

    getRank(): string {
        return (this.score / 10).toFixed( 0 );
    }
}

const getPosition = ( person: any ) => {
    if( person instanceof Student ) {
        // 使用 instanceof 类型守卫将 any 类型的 person 收窄为 Student
        // (parameter) person: Student
        console.log(
            `${ person.name } =>`, person.getGrade()
        );
    } else if( person instanceof Player ) {
        // 注意,这里要使用 else if 而不是 else
        // (parameter) person: Player
        console.log(
            `${ person.name } =>`, person.getRank()
        );
    }
};

getPosition( new Student( 'Ross Geller', 82 ) );
getPosition( new Student( 'Monica Geller', 71 ) );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

any类型在你无法得知一个值在运行时的形状时是相当有用的。这通常发生在你正在使用第三方 API 且 TypeScript 无法获得其类型时。为了禁止 TypeScript 编译器报错,你只能使用any类型。

unknown

TypeScript 3.0 引入unknown类型来缓解any有关的一些问题。最基础的,unknown类型告知 TypeScript 编译器,尽管值的类型是编译时是未知的(unknown),但是在运行时可能是任意类型。

因此,unknown可以表示任何any可以表示的值,这也让unknown成为顶级类型top type)或超类型supertype)。与any相似,一个包含了unknown类型的联合类型,将收窄为unknown类型,但是any的优先级更高。

// type Collection = unknown
type Collection = string | number | undefined | unknown;
// type Collection = any
type Collection = string | number | undefined | unknown | any;
// type Collection = string
type Collection = string & unknown;
1
2
3
4
5
6

但是,涉及到交叉类型,unknown的行为跟any有些不一样。任何类型与unknown类型进行交叉,返回的是原类型本身,因为交叉类型通过结合两个类型的形状创建了一个新的类型,而unknown不表示任何形状。

你可以将任何 JavaScript 的值赋值给unknown类型的变量,包括any类型的值。但是你不能将unknown类型的值赋值给已知类型的变量。

let x: unknown;
x = 1;
x = 'one';
x = true;

// Error: Type 'unknown' is not assignable to type 'boolean'.
let y: boolean = x;
1
2
3
4
5
6
7

针对上面代码最后一行 TypeScript 编译器会报错,因为y只能包含boolean类型的值,但unknown类型的值在运行时可以是任何类型。你可能争论说any的值在运行时也可以是任何类型,但这就是unknown类型引入的原因。

TypeScript 不会允许你在unknown类型的值上执行操作,除非使用类型断言或类型守卫收窄这个类型。

let x: unknown;
// Error: Property 'a' does not exist on type 'unknown'.
console.log( x.a );
// Error: This expression is not callable.
x();
// Error: This expression is not constructable.
new x();
1
2
3
4
5
6
7

any 和 unknown 的对比

unknownany最大的区别是,unknownany更加严格:

  • 我们在any类型的值上执行操作时,不需要进行任何类型检查;
  • 但是我们在unknown类型的值上执行绝大多数操作之前,必须要进行类型检查,以确保该类型的值可以执行对应的操作。
let value: any;

value = true;             // OK
value = 42;               // OK
value = "Hello World";    // OK
value = [];               // OK
value = {};               // OK
value = Math.random;      // OK
value = null;             // OK
value = undefined;        // OK
value = new TypeError();  // OK
value = Symbol("type");   // OK
1
2
3
4
5
6
7
8
9
10
11
12
let value: any;

// 以下的任何操作都是类型正确的
value.foo.bar;  // OK
value.trim();   // OK
value();        // OK
new value();    // OK
value[0][1];    // OK
1
2
3
4
5
6
7
8

尽管在any类型的值上执行任何操作都是类型正确的,但是在运行时可能会出错。unknown类型的产生,就是为了解决这个问题。

let value: unknown;

value = true;             // OK
value = 42;               // OK
value = "Hello World";    // OK
value = [];               // OK
value = {};               // OK
value = Math.random;      // OK
value = null;             // OK
value = undefined;        // OK
value = new TypeError();  // OK
value = Symbol("type");   // OK
1
2
3
4
5
6
7
8
9
10
11
12

any类型类似,将任何类型的值赋值给unknown,都是类型正确的。

但是,将unknown类型的值赋值给其他已知类型(除了anyunknown)的变量时,就会报错。

let value: unknown;

let value1: unknown = value;   // OK
let value2: any = value;       // OK
let value3: boolean = value;   // Error
let value4: number = value;    // Error
let value5: string = value;    // Error
let value6: object = value;    // Error
let value7: any[] = value;     // Error
let value8: Function = value;  // Error
1
2
3
4
5
6
7
8
9
10

同理,在unknown类型的值上执行操作,也会报错。

let value: unknown;

value.foo.bar;  // Error
value.trim();   // Error
value();        // Error
new value();    // Error
value[0][1];    // Error
1
2
3
4
5
6
7

这就是unknown类型的主要价值:TypeScript 不允许我们在unknown类型的值上随意执行操作。与之相反,我们需要先执行一些类型检查(比如类型守卫或类型断言)来收窄值的类型。

可在 unknown 上执行的操作

在不做类型检查的情况下,仅可在unknown类型的之上执行如下四种操作:

  • ===
  • ==
  • !==
  • !=

联合类型里的 unknown

any相似,包含了unknown的联合类型将收窄为unknown类型,但是any具有更高的优先级。

type UnionType1 = unknown | null;       // unknown
type UnionType2 = unknown | undefined;  // unknown
type UnionType3 = unknown | string;     // unknown
type UnionType4 = unknown | number[];   // unknown

type UnionType5 = unknown | any;  // any
1
2
3
4
5
6

交叉类型里的 unknown

任何类型与unknown类型交叉的结果,仍然是该类型自身。

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
1
2
3
4
5

与 void 和 never 的对比

void类型表示没有return语句的函数的返回类型,never类型表示永不返回任何值的函数的返回类型。voidnever都不表示运行时的任何值,它们只是作为 TypeScript 类型系统的辅助。

基于这个事实,voidnever不会表示任何anyunknown表示的值。但是,在一个包含了voidnever以及any(或unknown)的联合类型里,联合类型将收窄为anyunknown

// type Collection = any
type Collection = void | never | any;
// type Collection = unknown
type Collection = void | never | unknown;
1
2
3
4

参考文档