联合类型&类型保护
联合类型可以扩展变量的类型范围,但带来的代价是,类型检验会更麻烦。
比如:…
此时,需要类型保护机制来消除错误警告。
interface Bird {fly: booleansing: () => {}}interface Dog {fly: booleanbark: () => {}}function trainAnimal(animal: Bird | Dog) {// animal.sing() // 直接使用会报错!// 【类型保护】方案一:【类型断言】if (animal.fly) {(animal as Bird).sing()} else {(animal as Dog).bark()}// 【类型保护】方案二:【in语法】if ('sing' in animal) {animal.sing()} else {animal.bark()}}// 【类型保护】方案三:【typeof语法】function add(first: string | number, second: string | number) {if (typeof first === 'string' || typeof second === 'string') {return `${first} ${second}`} else {return first + second}}// 【类型保护】方案四:【instance语法】class NumberObj {// 注意,使用instance时,数据类型一定要通过类定义,而不能用接口。因为只有class才能使用instanceof操作符count: number}function addObj(first: object | NumberObj, second: object | NumberObj) {if (first instanceof NumberObj && second instanceof NumberObj) {return first.count + second.count}return 0}
枚举类型
enum Status {OFFLINE = 1,ONLINE,DELETED}console.log(Status.OFFLINE); // 1// enum很灵活的一点是:可以使用Status[0]【反查】到OFFLINEconsole.log(Status[0]); // OFFLINEfunction getResult(status) {if (status === Status.OFFLINE) {return 'offline';} else if (status === Status.ONLINE) {return 'online';} else if (status === Status.DELETED) {return 'deleted';}return 'error';}const result = getResult(1);console.log(result); // offline
泛型(重难点)
函数泛型
// 泛型 generic 泛指的类型// 理论上讲,泛型的名字可以随便起function join1<ABC>(first: ABC, second: ABC) {return `${first}${second}`;}// 但实践中常用 T, U, V, P等function join2<T, P>(first: T, second: P) {return `${first}${second}`;}function anotherJoin<T>(first: T, second: T): T {return first;}// 数组方案一:T[]function map1<T>(params: T[]) {return params}// 数组方案二:Array<T>function map2<T>(params: Array<T>) {return params;}join1<number>(1, 3);join2<number, string>(1, '1');map1<string>(['123']);
使用泛型做类型注解
// 如何使用泛型作为一个具体的类型注解function hello<T>(params: T) {return params;}const func: <T>(param: T) => T = hello;
类泛型
类泛型的基本使用
class DataManager<T> {constructor(private data: T[]) { }getItem(index: number): T {return this.data[index];}}const data = new DataManager<number>([1, 2, 3, 4, 5]);data.getItem(0);
使用extends对泛型做约束
interface Item {name: string;}class DataManager<T extends Item> {// T类型继承Item的含义:将来传进来的具体类型T,它必须有name这个字段!constructor(private data: T[]) { }getItemName(index: number): string {return this.data[index].name // 此时.name会有语法提示}}const data = new DataManager([{name: 'dell'}, {name: 'lee'}]);
使用extends对泛型做约束
interface Test {name: string;}class DataManager<T extends number | string> {constructor(private data: T[]) { }getItem(index: number): T {return this.data[index];}}const data1 = new DataManager<Test>([]) // 报错const data2 = new DataManager<number>([]); // 正确
keyof语法(难点中的难点)
interface Person {name: string;age: number;gender: string;}// type NAME = 'name';// key: 'name';// Person['name'];// type T = 'age'// key: 'age'// Person['age']// type T = 'gender'// key: 'gender'// Person['gender']class Teacher {constructor(private info: Person) { }// keyof 是个循环// 要想理解这,需要知道一点:类型可以是个固定的字符串!!!// type NAME='name' // 当类型为简单类型常量时,变量值只能取该常量,相当于使用const// const abc: NAME = 'name'getInfo<T extends keyof Person>(key: T): Person[T] {return this.info[key];}}const teacher = new Teacher({name: 'yyy',age: 3,gender: 'male'});const _name = teacher.getInfo('name'); // 将鼠标放在_name上,显示其类型为stringconst age = teacher.getInfo('age'); // 将鼠标放在age上,显示其类型为numberconsole.log(_name);
命名空间
namespace Home {class Header {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Header';document.body.appendChild(elem);}}class Content {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Content';document.body.appendChild(elem);}}class Footer {constructor() {const elem = document.createElement('div');elem.innerText = 'This is Footer';document.body.appendChild(elem);}}export class Page { // 将需要暴露的类或方法前面加上export!否则找不到!constructor() {new Header();new Content();new Footer();}}}
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><title>Document</title><script src="./dist/page.js"></script></head><body><script>new Home.Page();</script></body></html>
类型定义文件
小试牛刀
在html中引入两个文件(至于为什么可以直接引入.ts文件,请看:打包工具——Parcel)
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><title>类型定义文件</title><script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script><script src="./page.ts"></script></head><body></body></html>
其中 page.ts内容如下:
$(function() {$('body').html('<div>123</div>');});
此时项目是可以运行的,但是编辑器中会报错,因为不认识$这个符号。怎么办呢?写个类型声明文件:
// 定义全局变量// declare var $: (param: () => void) => void;// 定义全局函数interface JqueryInstance {html: (html: string) => JqueryInstance; // 该对象有一个html方法}// 重载 $declare function $(readyFunc: () => void): void; // 如果传入一个函数,返回空declare function $(selector: string): JqueryInstance; // 如果传入字符串,返回JqueryInstance类型的对象
ok,解决问题!
使用接口
interface JqueryInstance {html: (html: string) => JqueryInstance // 该对象有一个html方法}// 使用interface语法实现函数重载interface JQuery {(readyFunc: () => void): void(selector: string): JqueryInstance}declare var $: JQuery
模块化
import $ from 'jquery';$(function() {$('body').html('<div>123</div>');new $.fn.init();});
// Es6 模块化方案(还有commonjs、amd等语法的模块化方案)// 当 $ 既是函数,又是对象时(混合类型),可以使用 module语法declare module 'jquery' {interface JqueryInstance {html: (html: string) => JqueryInstance;}//function $(readyFunc: () => void): void;function $(selector: string): JqueryInstance;// 使用namespace实现对“对象”的定义,使用class在其内定义类namespace $ {namespace fn {class init { }}}export = $; // 最终把 $ 导出}
