联合类型&类型保护

联合类型可以扩展变量的类型范围,但带来的代价是,类型检验会更麻烦。
比如:…
此时,需要类型保护机制来消除错误警告。

  1. interface Bird {
  2. fly: boolean
  3. sing: () => {}
  4. }
  5. interface Dog {
  6. fly: boolean
  7. bark: () => {}
  8. }
  9. function trainAnimal(animal: Bird | Dog) {
  10. // animal.sing() // 直接使用会报错!
  11. // 【类型保护】方案一:【类型断言】
  12. if (animal.fly) {
  13. (animal as Bird).sing()
  14. } else {
  15. (animal as Dog).bark()
  16. }
  17. // 【类型保护】方案二:【in语法】
  18. if ('sing' in animal) {
  19. animal.sing()
  20. } else {
  21. animal.bark()
  22. }
  23. }
  24. // 【类型保护】方案三:【typeof语法】
  25. function add(first: string | number, second: string | number) {
  26. if (typeof first === 'string' || typeof second === 'string') {
  27. return `${first} ${second}`
  28. } else {
  29. return first + second
  30. }
  31. }
  32. // 【类型保护】方案四:【instance语法】
  33. class NumberObj {
  34. // 注意,使用instance时,数据类型一定要通过类定义,而不能用接口。因为只有class才能使用instanceof操作符
  35. count: number
  36. }
  37. function addObj(first: object | NumberObj, second: object | NumberObj) {
  38. if (first instanceof NumberObj && second instanceof NumberObj) {
  39. return first.count + second.count
  40. }
  41. return 0
  42. }

枚举类型

  1. enum Status {
  2. OFFLINE = 1,
  3. ONLINE,
  4. DELETED
  5. }
  6. console.log(Status.OFFLINE); // 1
  7. // enum很灵活的一点是:可以使用Status[0]【反查】到OFFLINE
  8. console.log(Status[0]); // OFFLINE
  9. function getResult(status) {
  10. if (status === Status.OFFLINE) {
  11. return 'offline';
  12. } else if (status === Status.ONLINE) {
  13. return 'online';
  14. } else if (status === Status.DELETED) {
  15. return 'deleted';
  16. }
  17. return 'error';
  18. }
  19. const result = getResult(1);
  20. console.log(result); // offline

泛型(重难点)

函数泛型

  1. // 泛型 generic 泛指的类型
  2. // 理论上讲,泛型的名字可以随便起
  3. function join1<ABC>(first: ABC, second: ABC) {
  4. return `${first}${second}`;
  5. }
  6. // 但实践中常用 T, U, V, P等
  7. function join2<T, P>(first: T, second: P) {
  8. return `${first}${second}`;
  9. }
  10. function anotherJoin<T>(first: T, second: T): T {
  11. return first;
  12. }
  13. // 数组方案一:T[]
  14. function map1<T>(params: T[]) {
  15. return params
  16. }
  17. // 数组方案二:Array<T>
  18. function map2<T>(params: Array<T>) {
  19. return params;
  20. }
  21. join1<number>(1, 3);
  22. join2<number, string>(1, '1');
  23. map1<string>(['123']);

使用泛型做类型注解

  1. // 如何使用泛型作为一个具体的类型注解
  2. function hello<T>(params: T) {
  3. return params;
  4. }
  5. const func: <T>(param: T) => T = hello;

类泛型

类泛型的基本使用

  1. class DataManager<T> {
  2. constructor(private data: T[]) { }
  3. getItem(index: number): T {
  4. return this.data[index];
  5. }
  6. }
  7. const data = new DataManager<number>([1, 2, 3, 4, 5]);
  8. data.getItem(0);

使用extends对泛型做约束

  1. interface Item {
  2. name: string;
  3. }
  4. class DataManager<T extends Item> {
  5. // T类型继承Item的含义:将来传进来的具体类型T,它必须有name这个字段!
  6. constructor(private data: T[]) { }
  7. getItemName(index: number): string {
  8. return this.data[index].name // 此时.name会有语法提示
  9. }
  10. }
  11. const data = new DataManager([
  12. {
  13. name: 'dell'
  14. }, {
  15. name: 'lee'
  16. }
  17. ]);

使用extends对泛型做约束

  1. interface Test {
  2. name: string;
  3. }
  4. class DataManager<T extends number | string> {
  5. constructor(private data: T[]) { }
  6. getItem(index: number): T {
  7. return this.data[index];
  8. }
  9. }
  10. const data1 = new DataManager<Test>([]) // 报错
  11. const data2 = new DataManager<number>([]); // 正确

keyof语法(难点中的难点)

  1. interface Person {
  2. name: string;
  3. age: number;
  4. gender: string;
  5. }
  6. // type NAME = 'name';
  7. // key: 'name';
  8. // Person['name'];
  9. // type T = 'age'
  10. // key: 'age'
  11. // Person['age']
  12. // type T = 'gender'
  13. // key: 'gender'
  14. // Person['gender']
  15. class Teacher {
  16. constructor(private info: Person) { }
  17. // keyof 是个循环
  18. // 要想理解这,需要知道一点:类型可以是个固定的字符串!!!
  19. // type NAME='name' // 当类型为简单类型常量时,变量值只能取该常量,相当于使用const
  20. // const abc: NAME = 'name'
  21. getInfo<T extends keyof Person>(key: T): Person[T] {
  22. return this.info[key];
  23. }
  24. }
  25. const teacher = new Teacher({
  26. name: 'yyy',
  27. age: 3,
  28. gender: 'male'
  29. });
  30. const _name = teacher.getInfo('name'); // 将鼠标放在_name上,显示其类型为string
  31. const age = teacher.getInfo('age'); // 将鼠标放在age上,显示其类型为number
  32. console.log(_name);

命名空间

  1. namespace Home {
  2. class Header {
  3. constructor() {
  4. const elem = document.createElement('div');
  5. elem.innerText = 'This is Header';
  6. document.body.appendChild(elem);
  7. }
  8. }
  9. class Content {
  10. constructor() {
  11. const elem = document.createElement('div');
  12. elem.innerText = 'This is Content';
  13. document.body.appendChild(elem);
  14. }
  15. }
  16. class Footer {
  17. constructor() {
  18. const elem = document.createElement('div');
  19. elem.innerText = 'This is Footer';
  20. document.body.appendChild(elem);
  21. }
  22. }
  23. export class Page { // 将需要暴露的类或方法前面加上export!否则找不到!
  24. constructor() {
  25. new Header();
  26. new Content();
  27. new Footer();
  28. }
  29. }
  30. }
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <title>Document</title>
  6. <script src="./dist/page.js"></script>
  7. </head>
  8. <body>
  9. <script>
  10. new Home.Page();
  11. </script>
  12. </body>
  13. </html>

类型定义文件

小试牛刀

在html中引入两个文件(至于为什么可以直接引入.ts文件,请看:打包工具——Parcel

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <title>类型定义文件</title>
  6. <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
  7. <script src="./page.ts"></script>
  8. </head>
  9. <body></body>
  10. </html>

其中 page.ts内容如下:

  1. $(function() {
  2. $('body').html('<div>123</div>');
  3. });

此时项目是可以运行的,但是编辑器中会报错,因为不认识$这个符号。怎么办呢?写个类型声明文件:

  1. // 定义全局变量
  2. // declare var $: (param: () => void) => void;
  3. // 定义全局函数
  4. interface JqueryInstance {
  5. html: (html: string) => JqueryInstance; // 该对象有一个html方法
  6. }
  7. // 重载 $
  8. declare function $(readyFunc: () => void): void; // 如果传入一个函数,返回空
  9. declare function $(selector: string): JqueryInstance; // 如果传入字符串,返回JqueryInstance类型的对象

ok,解决问题!

使用接口

  1. interface JqueryInstance {
  2. html: (html: string) => JqueryInstance // 该对象有一个html方法
  3. }
  4. // 使用interface语法实现函数重载
  5. interface JQuery {
  6. (readyFunc: () => void): void
  7. (selector: string): JqueryInstance
  8. }
  9. declare var $: JQuery

模块化

  1. import $ from 'jquery';
  2. $(function() {
  3. $('body').html('<div>123</div>');
  4. new $.fn.init();
  5. });
  1. // Es6 模块化方案(还有commonjs、amd等语法的模块化方案)
  2. // 当 $ 既是函数,又是对象时(混合类型),可以使用 module语法
  3. declare module 'jquery' {
  4. interface JqueryInstance {
  5. html: (html: string) => JqueryInstance;
  6. }
  7. //
  8. function $(readyFunc: () => void): void;
  9. function $(selector: string): JqueryInstance;
  10. // 使用namespace实现对“对象”的定义,使用class在其内定义类
  11. namespace $ {
  12. namespace fn {
  13. class init { }
  14. }
  15. }
  16. export = $; // 最终把 $ 导出
  17. }