原始类型的类型标注
在 TypeScript 中原始类型对应的类型注解:
const name: string = 'linbudu';
const age: number = 24;
const male: boolean = false;
const undef: undefined = undefined;
const nul: null = null;
const obj: object = { name, age, male };
const bigintVar1: bigint = 9007199254740991n;
const bigintVar2: bigint = BigInt(9007199254740991);
const symbolVar: symbol = Symbol('unique');
其中,除了 null 与 undefined 以外,余下的类型基本上可以完全对应到 JavaScript 中的数据类型概念,因此这里我们只对 null 与 undefined 展开介绍。
null 与 undefined
在 JavaScript 中,null 表示“这里有值,但是个空值”,undefined 表示“这里没有值”。
在 TypeScript 中,null 与 undefined 类型都是有具体意义的类型。也就是说,它们作为类型时,表示的是一个有意义的具体类型值。这两者在没有开启 strictNullChecks 检查的情况下,会被视作其他类型的子类型,比如 string 类型会被认为包含了 null 与 undefined 类型:
const tmp1: null = null;
const tmp2: undefined = undefined;
const tmp3: string = null; // 仅在关闭 strictNullChecks 时成立,下同
const tmp4: string = undefined;
void
JavaScript中,void 操作符会执行后面跟着的表达式并返回一个 undefined,void 操作符强制将后面的函数声明转化为了表达式。
void function iife() {
console.log("Invoked!");
}();
<a href="javascript:void(0)">清除缓存</a>
TypeScript当中,void 用于描述一个内部没有 return 语句,或者没有显式 return 一个值的函数的返回值
function func1() {}
function func2() {
return;
}
function func3() {
return undefined;
}
数组的类型标注
在 TypeScript 中有两种方式来声明一个数组类型:
const arr1: string[] = [];
const arr2: Array<string> = [];
元组(Tuple)
如一个数组中只存放固定长度的变量,但我们进行了超出长度地访问:
const arr3: string[] = ['lin', 'bu', 'du'];
console.log(arr3[599]);
这是它并不会报错也不会有任何提示,显然是不符合预期的。因为我们能确定这个数组中只有三个成员,并希望在越界访问时给出类型报错。这时我们可以使用元组类型进行类型标注:
const arr4: [string, string, string] = ['lin', 'bu', 'du'];
console.log(arr4[599]);
此时将会产生一个类型错误:长度为“3”的元组类型“[string, string, string]”在索引“599“处没有元素。除了同类型的元素以外,元组内部也可以声明多个与其位置强绑定的,不同类型的元素:
const arr5: [string, number, boolean] = ['linbudu', 599, true];
对数组合法边界内的索引访问(即 0、1、2)将精确地获得对应位置上的类型。同时元组也支持了在某一个位置上的可选成员:
const arr6: [string, number?, boolean?] = ['linbudu'];
// 下面这么写也可以
// const arr6: [string, number?, boolean?] = ['linbudu', , ,];
对于标记为可选的成员,在 —strictNullCheckes 配置下会被视为一个 string | undefined 的类型。此时元组的长度属性也会发生变化,比如上面的元组 arr6 ,其长度的类型为 1 | 2 | 3:
type TupleLength = typeof arr6.length; // 1 | 2 | 3
具名元组
你可能会觉得,元组的可读性实际上并不好。比如对于 [string, number, boolean]来说,你并不能直接知道这三个元素都代表什么,还不如使用对象的形式。
而在 TypeScript 4.0 中,有了具名元组(Labeled Tuple Elements)的支持,使得我们可以为元组中的元素打上类似属性的标记:
const arr7: [name: string, age: number, male: boolean] = ['linbudu', 599, true];
具名元组可选元素的修饰符将成为以下形式:
const arr7: [name: string, age: number, male?: boolean] = ['linbudu', 599, true];
解构赋值
除了显式地越界访问,还可能存在隐式地越界访问,如通过解构赋值的形式:
const arr1: string[] = [];
const [ele1, ele2, ...rest] = arr1;
对于数组,此时仍然无法检查出是否存在隐式访问,因为类型层面并不知道它到底有多少个元素。
但对于元组,隐式的越界访问也能够被揪出来给一个警告:
const arr5: [string, number, boolean] = ['linbudu', 599, true];
// 长度为 "3" 的元组类型 "[string, number, boolean]" 在索引 "3" 处没有元素。
const [username, age, male, other] = arr5;
对象的类型标注
类似于数组类型,在 TypeScript 中我们也需要特殊的类型标注来描述对象类型,即 interface ,你可以理解为它代表了这个对象对外提供的接口结构。
首先我们使用 interface 声明一个结构,然后使用这个结构来作为一个对象的类型标注即可:
interface IDescription {
name: string;
age: number;
male: boolean;
}
const obj1: IDescription = {
name: 'linbudu',
age: 599,
male: true,
};
这里的“描述”指:
- 每一个属性的值必须一一对应到接口的属性类型
- 不能有多的属性,也不能有少的属性,包括直接在对象内部声明,或是 obj1.other = ‘xxx’ 这样属性访问赋值的形式
修饰接口属性
可选属性
接口结构中同样通过 ? 来标记一个属性为可选: ```javascript interface IDescription { name: string; age: number; male?: boolean; func?: Function; }
const obj2: IDescription = { name: ‘linbudu’, age: 599, male: true, // 无需实现 func 也是合法的 };
在这种情况下,即使你在 obj2 中定义了 male 属性,但当你访问 obj2.male 时,它的类型仍然会是 boolean | undefined<br />假设新增一个可选的函数类型属性,然后进行调用:obj2.func() ,此时将会产生一个类型报错:_**不能调用可能是未定义的方法**_。但可选属性标记不会影响你对这个属性进行赋值,如:
```javascript
obj2.male = false;
obj2.func = () => {};
即使你对可选属性进行了赋值,TypeScript 仍然会使用接口的描述为准进行类型检查,你可以使用类型断言、非空断言或可选链解决
public
protected
readonly
它的作用是防止对象的属性被再次赋值。
interface IDescription {
readonly name: string;
age: number;
}
const obj3: IDescription = {
name: 'linbudu',
age: 599,
};
// 无法分配到 "name" ,因为它是只读属性
obj3.name = "林不渡";
private
static
type 与 interface
interface 用来描述对象、类的结构
类型别名用来将一个函数签名、一组联合类型、一个工具类型等等抽离成一个完整独立的类型
object、Object 以及 { }
在 TypeScript 中就表现为 Object 包含了所有的类型:
// 对于 undefined、null、void 0 ,需要关闭 strictNullChecks
const tmp1: Object = undefined;
const tmp2: Object = null;
const tmp3: Object = void 0;
const tmp4: Object = 'linbudu';
const tmp5: Object = 599;
const tmp6: Object = { name: 'linbudu' };
const tmp7: Object = () => {};
const tmp8: Object = [];
和 Object 类似的还有 Boolean、Number、String、Symbol,这几个装箱类型(Boxed Types) 同样包含了一些超出预期的类型。以 String 为例,它同样包括 undefined、null、void,以及代表的 拆箱类型(Unboxed Types) string,但并不包括其他装箱类型对应的拆箱类型,如 boolean 与 基本对象类型,我们看以下的代码:
const tmp9: String = undefined;
const tmp10: String = null;
const tmp11: String = void 0;
const tmp12: String = 'linbudu';
// 以下不成立,因为不是字符串类型的拆箱类型
const tmp13: String = 599; // X
const tmp14: String = { name: 'linbudu' }; // X
const tmp15: String = () => {}; // X
const tmp16: String = []; // X
object 的引入就是为了解决对 Object 类型的错误使用,它代表所有非原始类型的类型,即数组、对象与函数类型这些:
const tmp17: object = undefined;
const tmp18: object = null;
const tmp19: object = void 0;
const tmp20: object = 'linbudu'; // X 不成立,值为原始类型
const tmp21: object = 599; // X 不成立,值为原始类型
const tmp22: object = { name: 'linbudu' };
const tmp23: object = () => {};
const tmp24: object = [];
最后是{},一个奇奇怪怪的空对象,如果你了解过字面量类型,可以认为{}就是一个对象字面量类型(对应到字符串字面量类型这样)。否则,你可以认为使用{}作为类型签名就是一个合法的,但内部无属性定义的空对象,这类似于 Object(想想 new Object()),它意味着任何非 null / undefined 的值:
const tmp25: {} = undefined; // 仅在关闭 strictNullChecks 时成立,下同
const tmp26: {} = null;
const tmp27: {} = void 0; // void 0 等价于 undefined
const tmp28: {} = 'linbudu';
const tmp29: {} = 599;
const tmp30: {} = { name: 'linbudu' };
const tmp31: {} = () => {};
const tmp32: {} = [];
虽然能够将其作为变量的类型,但你实际上无法对这个变量进行任何赋值操作
const tmp30: {} = { name: 'linbudu' };
tmp30.age = 18; // X 类型“{}”上不存在属性“age”。