Ts_06 类型系统深入

类型保护

我们通常在 JavaScript 中通过判断来处理一些逻辑,在 TypeScript 中这种条件语句块还有另外一 个特性:根据判断逻辑的结果,缩小类型范围(有点类似断言),这种特性称为 类型保护 ,触发条 件:

  • 逻辑条件语句块:if、else、elseif
  • 特定的一些关键字:typeof、instanceof、in……

typeof

我们知道 typeof 可以返回某个数据的类型,在 TypeScript 在 if 、 else 代码块中能够把 typeof 识别为类型保护,推断出适合的类型

  1. function fn(a: string|number) { // error,不能保证 a 就是字符串 a.substring(1);
  2. if (typeof a === 'string') {
  3. // ok
  4. a.substring(1);
  5. } else {
  6. // ok
  7. a.toFixed(1);
  8. }

instanceof

与 typeof 类似的, instanceof 也可以被 TypeScript 识别为类型保护

  1. function fn(a: Date|Array<any>) {
  2. if (a instanceof Array) {
  3. a.push(1);
  4. } else {
  5. a.getFullYear();
  6. }
  7. }

in

in 也是如此

  1. interface IA {
  2. x: string;
  3. y: string;
  4. }
  5. interface IB {
  6. a: string;
  7. b: string; }
  8. function fn(arg: IA | IB) {
  9. if ('x' in arg) {
  10. // ok
  11. arg.x;
  12. // error
  13. arg.a;
  14. } else { // ok
  15. arg.a;
  16. // error
  17. }

字面量类型保护

如果类型为字面量类型,那么还可以通过该字面量类型的字面值进行推断

  1. interface IA {
  2. type: 'IA';
  3. x: string;
  4. y: string; }
  5. interface IB {
  6. type: 'IB';
  7. a: string;
  8. b: string; }
  9. function fn(arg: IA | IB) {
  10. if (arg.type === 'IA') {
  11. // ok
  12. arg.x;
  13. // error
  14. arg.a;
  15. } else { // ok
  16. arg.a;
  17. // error
  18. arg.x;
  19. }
  20. }

自定义保护类型

有的时候,以上的一些方式并不能满足一些特殊情况,则可以自定义类型保护规则

  1. function canEach(data: any): data is Element[]|NodeList {
  2. return data.forEach !== undefined;
  3. }
  4. function fn2(elements: Element[]|NodeList|Element) {
  5. if ( canEach(elements) ) {
  6. elements.forEach((el: Element)=>{
  7. el.classList.add('box');
  8. });
  9. } else {
  10. elements.classList.add('box');
  11. }
  12. }

data is Element[]|NodeList 是一种类型谓词,格式为: xx is XX ,返回这种类型的函数就可以 被 TypeScript 识别为类型保护

类型操作

TypeScript 提供了一些方式来操作类型这种数据,但是需要注意的是,类型数据只能作为类型来使 用,而不能作为程序中的数据,这是两种不同的数据,一个用在编译检测阶段,一个用于程序执行阶段

typeof

在 TypeScript 中, typeof 有两种作用

  • 获取数据的类型
  • 捕获数据的类型
  1. let str1 = 'kaikeba';
  2. // 如果是 let ,把 'string' 作为值
  3. let t = typeof str1;
  4. // 如果是 type,把 'string' 作为类型
  5. type myType = typeof str1;
  6. let str2: myType = '开课吧';
  7. let str3: typeof str1 = '开课吧';

keyof

获取类型的所有 key 的集合

  1. interface Person {
  2. name: string;
  3. age: number;
  4. };
  5. type personKeys = keyof Person;
  6. // 等同:type personKeys = "name" | "age"
  7. let p1 = {
  8. name: 'zMouse',
  9. age: 35 }
  10. function getPersonVal(k: personKeys) {
  11. return p1[k];
  12. }
  13. /**
  14. 等同:
  15. function getPersonVal(k: 'name'|'age') {
  16. return p1[k];
  17. }
  18. */
  19. getPersonVal('name'); //正确
  20. getPersonVal('gender'); //错误

in

针对类型进行操作的话,内部使用的 for…in 对类型进行遍历

  1. interface Person {
  2. name: string;
  3. age: number;
  4. }
  5. type personKeys = keyof Person;
  6. type newPerson = {
  7. [k in personKeys]: number;
  8. /**
  9. 等同 [k in 'name'|'age']: number; 也可以写成
  10. [k in keyof Person]: number;
  11. */
  12. }
  13. /**
  14. type newPerson = {
  15. name: number;
  16. age: number;
  17. }
  18. */

注意: in 后面的类型值必须是 string 或者 number 或者 symbol

类型兼容

TypeScript 的类型系统是基于结构子类型的,它与名义类型(如:java)不同(名义类型的数据类型 兼容性或等价性是通过明确的声明或类型的名称来决定的)。这种基于结构子类型的类型系统是基于组 成结构的,只要具有相同类型的成员,则两种类型即为兼容的。

  1. class Person {
  2. name: string;
  3. age: number;
  4. }
  5. class Cat {
  6. name: string;
  7. age: number;
  8. }
  9. function fn(p: Person) {
  10. p.name;
  11. }
  12. let xiaohua = new Cat();
  13. // ok,因为 Cat 类型的结构与 Person 类型的结构相似,所以它们是兼容的
  14. fn(xiaohua);