其他常见类型
any
如果是一个普通类型,在赋值过程中改变类型是不被允许的
let number: string = 'seven';
number = 7; // 不能将类型“number”分配给类型“string”。
但如果是 any 类型,则允许被赋值为任意类型
let number: any = 'seven';
number = 7;
在任意值上访问任何属性和方法都是允许的
let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName());
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
let number;
number = 'seven';
number = 7;
当你不希望某个特定的值导致类型检查错误时,你可以使用它。这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。
unknown
共同点,它跟 any 一样,可以是任何类型:
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
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
unknown 类型只能分配给类型 any 和 unknown 类型本身。
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
和 any 主要区别是 unknown 类型会更加严格:unknown 类型被确定是某个类型之前,它不能被进行任何操作比如实例化、getter、函数执行等等。而 any 是可以的,这也是为什么说 unknown 是更安全的 any。
let value: any;
value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK
let value: unknown;
value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error
never
never 表示不存在的值的类型。我们可以声明一个 never 类型的变量,但是无法给这个变量赋任何类型的值(除了never本身之外)
既然如此,never 的用途是什么呢?它可以用于指定一个永远无法返回的函数的返回类型。什么函数不会返回值呢?
比如:错误处理函数,无限循环函数。
// 抛出异常的函数永远不会有返回值
function fail(message: string): never {
throw new Error(message);
}
// 无限循环函数永远不会有返回值
function finish():never{
while(true){
console.log('finish')
}
}
由于 never 仅能被赋值给另外一个 never 类型,因此你还可以用它来进行编译时的全面的检查,用来防止代码不小心到达这里。
interface Foo {
type: 'foo'
}
interface Bar {
type: 'bar'
}
type All = Foo | Bar
function handleValue(val: All) {
switch (val.type) {
case 'foo':
// 这里 val 被收窄为 Foo
break
case 'bar':
// val 在这里是 Bar
break
default:
// val 在这里是 never
const exhaustiveCheck: never = val
break
}
}
注意在 default 里面我们把被收窄为 never 的 val 赋值给一个显式声明为 never 的变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事改了 All 的类型:
type All = Foo | Bar | Baz
然而他忘记了在 handleValue 里面加上针对 Baz 的处理逻辑,这个时候在 default branch 里面 val 会被收窄为 Baz,导致无法赋值给 never,产生一个编译错误。所以通过这个办法,你可以确保 handleValue 总是穷尽 (exhaust) 了所有 All 的可能类型。
与 void 的差异
void 表示没有任何类型,never 表示永远不存在的值的类型。
当一个函数返回空值时,它的返回值为 void 类型,但是,当一个函数永不返回时(或者总是抛出错误),它的返回值为 never 类型。void 类型可以被赋值,但是 never 除了 never 本身以外,其他任何类型不能赋值给 never。
数组
在 TypeScript 中,数组类型有多种定义方式,比较灵活。
类型 + 方括号
let fibonacci: number[] = [1, 1, 2, 3, 5];
数组的项中不允许出现其他的类型:
let fibonacci: number[] = [1, '1', 2, 3, 5];
// 不能将类型“string”分配给类型“number”。
数组的一些方法的参数也会根据数组在定义时约定的类型进行限制
let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push('8');
// 类型“string”的参数不能赋给类型“number”的参数。
数组泛型
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
用接口表示数组
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
NumberArray 表示:只要索引的类型是数字时,那么值的类型必须是数字。
虽然接口也可以用来描述数组,但是我们一般不会这么做,因为这种方式比前两种方式复杂多了。
不过有一种情况例外,那就是它常用来表示类数组
类数组
类数组(Array-like Object)不是数组类型,比如 arguments:
function sum() {
let args: number[] = arguments;
}
// 类型“IArguments”缺少类型“number[]”的以下属性: pop, push, concat, join 及其他 23 项。
上例中,arguments 实际上是一个类数组,不能用普通的数组的方式来描述,而应该用接口:
function sum() {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}
在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有 length 和 callee 两个属性。
事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等:
元组(Tuple)
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
定义一对值分别为 string 和 number 的元组:
let tom: [string, number] = ['Tom', 25];
当赋值或访问一个已知索引的元素时,会得到正确的类型:
let tom: [string, number];
tom[0] = 'Tom';
tom[1] = 25;
tom[0].slice(1);
tom[1].toFixed(2);
当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项
let tom: [string, number];
tom = ['Tom', 25];
当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型:
let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');
tom.push(true); // 类型“boolean”的参数不能赋给类型“string | number”的参数。