webpack 构建 TS 项目
安装依赖
安装 webpack npm install webpack -D
webpack4 以上需要 npm install webpack-cli -D
编译 TS npm install ts-loader -D
TS 环境 npm install typescript -D
热更新服务 npm install webpack-dev-server -D
HTML 模板 npm install html-webpack-plugin -D
配置文件
const path = require('path') const htmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: "./src/index.ts", mode: "development", output: { path: path.resolve(__dirname, './dist'), filename: "index.js" }, stats: "none", resolve: { extensions: ['.ts', '.js'], alias: { '@': path.resolve(__dirname, './src') } }, module: { rules: [ { test: /\.ts$/, use: "ts-loader" } ] }, devServer: { port: 1988, proxy: {} }, plugins: [ new htmlWebpackPlugin({ template: "./public/index.html" }) ] }
目录结构
实战TS编写发布订阅模式(TS -- 22)
什么是发布订阅模式,其实小伙伴已经用到了发布订阅模式例如 addEventListener,Vue evnetBus
都属于发布订阅模式
简单来说就是 你(小满)要和 大满 二满 三满打球,大满带球,二满带水,三满带球衣。全都准备完成后开始打球。
思维导图
interface Evenet{ on:(name:string,fn:Function)) => void, emit(name:string,..args:Array<any>) => void,//派发 off(name:string,fn:Function)=> void,//移除 once(name:string,fn:Function) => void//只执行一次 } interface List{ [key:string]:Array<Function> } class Dispatch implements Event{//通过implements来约束这个类(Evenet) list:List constructor(){ this.list = {} } on(name:string,fn:Function){ const callback = this.list[name] || []//如果有取到值的话那就是一个数组,没有取到值的话就是一个空数组 callback.push(fn)//因为不管怎么说,callback都是数组,所以我们后面的数组也可以直接添加上去 this.list[name] = callback console.log(this.list); } emit(name:string,..args:Array<any>){ let evnetName = this.list[name] //on监听跟emit派发的时候 name 是需要一样的,不然会出错,所以我们这里要进行一个判断 if(evnetName){ eventName.forEach(fn=>{ //内容从下面的o.emit()传送上来 fn.apply(this.args)//第一个参数this指向,第二个参数为数组,这里也刚好是一个数组就直接传进去。会将on监听的数据直接打印出来,这里打印出来66 99 }) }else{ console.error(`名称错误${name}`) } } off(name:string,fn:Function){ //off是删除一个函数,所以我们在下面将创建一个fn函数让他来删一下 let eventName = this.list[name] //跟emit一样的,需要进行判断有没有值,还有就是函数存不存在,不存在的话就没得删了对吧 if(eventName && fn){ //我们要通过索引来将其删掉 let index = eventName.findIndex(fns=> fns === fn ) eventName.splice(index,1) console.log(eventName) }else{ console.error(`名称错误${name}`) } } once(name:string,fn:Function){ let de = (...args:Array<any>) =>{ fn.apply(this,args)//指向到那个只调用一次函数的那里 this.off(name,de)//调用完就把它删掉,这就是只能调用一次的原因哈哈 } this.on(name,de)//第一个还是名字,第二个临时函数 } } const o = new Dispatch()//初始化 o.on('post',()=>{//post作为key console.log(66); })//第一个参数是事件名称,第二个是回调函数 o.on('post',(...args:Array<any>)=>{ console.log(99,args) //这里我们对第二个回调函数传入了...args,也就是收到了o.emit除了第一个参数后面那些乱七八糟的东西(因为我们设定了any,对接收的类型并没有限制,所以收到什么乱七八糟的东西都不奇怪),并在控制台打印了出来 }) const fn = (...args:Array<any>) => { console.log(args,2) } o.on('post',fn)//没错,这个就是特地创建出来删掉的 o.off('post',fn)//将fn删掉 //o.on('post2',()=>{ //都会在控制台显示出来 //}) o.once('post',(...args:Array<any>)=>{ console.log(args,'once') }) o.emit('post',1,false,{name:"小满"})//除了第一个参数一样是事件,后面参数是不限制个数的,而且传什么都行 o.emit('post',2,false,{name:"小满"})//这里如果收到就是有问题的,因为我们在上面使用once了,只调用一次 上面在off删除中使用到的splice知识点补充 splice(index,len,[item]) 它也可以用来替换 / 删除 / 添加数组内某一个或者几个值(该方法会改变原始数组) index: 数组开始下标 len: 替换 / 删除的长度 item: 替换的值,删除操作的话 item 为空 删除: // 删除起始下标为 1,长度为 1 的一个值 (len 设置 1,如果为 0,则数组不变) var arr = ['a','b','c','d']; arr.splice(1,1); console.log(arr); //['a','c','d']; // 删除起始下标为 1,长度为 2 的一个值 (len 设置 2) var arr2 = ['a','b','c','d'] arr2.splice(1,2); console.log(arr2); //['a','d'] 替换: // 替换起始下标为 1,长度为 1 的一个值为‘ttt’,len 设置的 1 var arr = ['a','b','c','d']; arr.splice(1,1,'ttt'); console.log(arr); //['a','ttt','c','d'] // 替换起始下标为 1,长度为 2 的两个值为‘ttt’,len 设置的 1 var arr2 = ['a','b','c','d']; arr2.splice(1,2,'ttt'); console.log(arr2); //['a','ttt','d'] 添加: // 在下标为 1 处添加一项 'ttt' var arr = ['a','b','c','d']; arr.splice(1,0,'ttt'); console.log(arr); //['a','ttt','b','c','d']
TS 进阶用法 proxy & Reflect(TS -- 23)
proxy:对象代理(是ES6新增的对象拦截器,能够监听到一个对象的变化)
Reflect:配合proxy来操作对象
Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
target
要使用 Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p
的行为。
handler.get()
本次使用的 get
属性读取操作的捕捉器。
handler.set()
本次使用的 set
属性设置操作的捕捉器。
Reflect
与大多数全局对象不同 Reflect
并非一个构造函数,所以不能通过 new 运算符对其进行调用,或者将 Reflect
对象作为一个函数来调用。Reflect
的所有属性和方法都是静态的(就像 Math 对象)
Reflect.get(target, name, receiver)
Reflect.get
方法查找并返回 target
对象的 name
属性,如果没有该属性返回 undefined
Reflect.set(target, name,value, receiver)
Reflect.set
方法设置 target
对象的 name
属性等于 value
。
type Person{ name:string, age:number, text:string } const proxy = (object:any,key:any)=>{//我们要自己实现proxy啦 return new Proxy(object,{ get(target,prop,receiver){ console.log('================>get',prop); //prop就是一个key,target就是地下那个man的对象,receiver是跟target一样的值,防止上下文错误的 return Reflect.get(target,prop,receiver)//这里刚好对应的也是这三个参数 } set(target,prop,value,receiver){//多了一个value,因为我们要设置值 //日志 console.log('================>set',prop); return Reflect.get(target,prop,value,receiver) } }) } //日志监听函数 //由于我们要监听man里面的内容,所以这里可以使用联合类型 const logAccess = <T>(object: T ,key:"name" | "age" | "text"):T =>{//为了使其灵活度高一点,我们不使其object等于Person,而是为泛型T,使用的时候再去设置 return proxy(object,key) } let man:Person = ({ name:"小满" age:22 text:"三秒真男人" },'name') let man2 = logAccess({ name:"小余" },'name') man.age = 30//走set man.age//走get console.log(man) 泛型优化 const logAccess = <T>(object: T ,key:keyof T):T =>{//为了使其灵活度高一点,我们不使其object等于Person,而是为泛型T,使用的时候再去设置。key也不固定死,而是使用keyof,将我们传入的对象推断为联合类型 return proxy(object,key) } let man2 = logAccess({ name:"小余" id:925 },'id')//就可以动态的去约束类型 let man2 = logAccess({ name:"小余" id:925 },'id2')//报错,因为我们类型里没有id2 优化完整版 type Person = { name: string, age: number, text: string } const proxy = (object: any, key: any) => { return new Proxy(object, { get(target, prop, receiver) { console.log(`get key======>${key}`); return Reflect.get(target, prop, receiver) }, set(target, prop, value, receiver) { console.log(`set key======>${key}`); return Reflect.set(target, prop, value, receiver) } }) } const logAccess = <T>(object: T, key: keyof T): T => { return proxy(object, key) } let man: Person = logAccess({ name: "小满", age: 20, text: "我的很小" }, 'age') let man2 = logAccess({ id:1, name:"小满2" }, 'name') man.age = 30 console.log(man);
TS 进阶用法 Partial & Pick(TS -- 24)
TypeScript内置高级类型Partial Pick
Partial
源码
/** * Make all properties in T optional 将T中的所有属性设置为可选 */ type Partial<T> = { [P in keyof T]?: T[P]; }; 手写实现 type Person{ name:string, age:number, text:string } //keyof:将一个接口对象的全部属性取出来变成联合类型 //keyof的作用就是把我们的属性变成联合类型,在底下就相当于"name"|"age"|"text"。而 in 就是为遍历这个联合类型的每一项,然后放到这个P里(所以P里就是name、age、text),然后使其变成`?`可选的 type Par<T> = {//这个T就是我们传过来的Person,所以T[P]就是Person里面name、age、text的内容(string那些啥的) //小满对T[P]的形容方式:通过索引取值的方式 [P in keyof T]?:T[P]//所以你在看这个肯定能看懂 }; type p = Partial<Person>//这个时候,我们会发现p上面的属性,name、age、text都变成可选的了 使用前(范例) type Person = { name:string, age:number } type p = Partial<Person> 使用后(范例) type p = { name?: string | undefined; age?: number | undefined; }
Pick
从类型定义 T 的属性中,选取指定一组属性,返回一个新的类型定义。
源码
/** * From T, pick a set of properties whose keys are in the union K */ type Pick<T, K extends keyof T> = { [P in K]: T[P]; }; 手写实现 type Person = { name:string, age:number, text:string, address:string } type Ex = "text" | "age" type A = Pick<Person,Ex> 分析(需要结合手写的内容看) type Pick<T, K extends keyof T> = {//T跟K都是泛型,T相当于我们的Person,K就相当于我们传的联合类型,然后同样也经历了keyof的洗礼,使其变成联合类型,K通过extends被约束了这点,使其只能为T,也就是Person内的值 [P in K]: T[P]; }; type p = Pick<Person,'age'|'name'>//这里的Person请参考手写实现的Person
TS 进阶用法 Record & Readonly(TS -- 25)
Readonly
和 Partial 很像是吧?只是将Partial替换成了 Readonly
源码 type Readonly<T> = { readonly [P in keyof T]: T[P]; }; 手写实现 type Readonly<T> = { readonly [P in keyof T]: T[P];//keyof还是那样,转化为联合类型,in去遍历选项。T[P]通过索引取值的方式 //然后为里面每个内容都加上只读属性 }; type Person = { name:string, age:number, text:string } type man = R<Person>
Record
1 keyof any 返回 string number symbol 的联合类型
2 in 我们可以理解成 for in P 就是 key 遍历 keyof any 就是 string number symbol 类型的每一项
3 extends 来约束我们的类型
4 T 直接返回类型
做到了约束 对象的 key 同时约束了 value
源码 type Record<K extends keyof any, T> = { [P in K]: T; }; 手写实现 type Rec<K extends keyof any,T> = {//T是泛型,传什么在这里并没有限制 [P in K]: T; }; //keyof返回联合类型 type key = string |number | symbol type Person ={ name:string, age:number. text:string } type K = "A"|"B"|"C"//因为我们在这里定义了K,所以let B才只能使用A、B、C,如果这里换成1、2、3,那底下也只能使用1、2、3而不是A、B、C type B = Rec<K,Person>//这里会返回成type B = {A:Person;B:Person;C:Person;}的形式 let obj:B = { A:{name:"小满",age:3,text:"三秒真男人"}//这里值的类型需要是Person的类型,因为在type B中已经定义了 B:{name:"小余",age:18,text:"三小时真男人"} C:{name:"狗洛",age:1,text:"零点三秒真男人"} }
TS 进阶用法 infer(TS -- 26)
infer 是 TypeScript新增到的关键字 充当占位符
我们来实现一个条件类型推断的例子
定义一个类型 如果是数组类型 就返回 数组元素的类型 否则 就传入什么类型 就返回什么类型
type TYPE<T> = T entends Array<any> ? T[number] : T type A = TYPE<string[]>//会返回type A = string type B = TYPE<(string|number)[]>//会返回type B = string|number type C = TYPE<boolean>//返回type C = boolean infer 使用 inter 修改 type TYPE<T> = T entends Array<infer U> ? U : T//U不是泛型,而是充当占位符使用,读取Array类型然后进行返回 type TYPE<T> = T entends Array<infer U> ? U : never//限制只能传type T这个元组类型,其他都不能传 type A = TYPE<string[]>//会返回type A = string type B = TYPE<(string|number)[]>//会返回type B = string|number type T = [string,number] //使其变成联合类型(小技巧) type uni = TYPE<T>//返回联合类型type uni = string|number type uni = TYPE<T>//返回type uni = never。因为我们进行了限制
infer 类型提取(TS -- 27)
提取头部元素
T extends any[]
:对T进行泛型约束,一个any类型的数组
type First<T extends any[]> = T extends [infer one,infer two,infer three]
? one:[]:对T进行泛型约束为数组类型,用infer提取,提取的变量名对应着Arr的a、b、c。然后决定返回one,也就是第一个元素还是空数组
type Arr = ['a','b','c'] type First<T extends any[]> = T extends [infer one,infer two,infer three]? one:[] //ES6进阶版 type First<T extends any[]> = T extends [infer First,...any[]] ? First : []//1 ... type a = First<Arr>
提取尾部元素
将头尾反过来了
type Arr = ['a', 'b', 'c'] type Last<T extends any[]> = T extends [...any[], infer Last] ? Last : []//... 尾部 type c = Last<Arr>
剔除第一个元素 Shift
将除了第一个之外的其他通过ES6语法提取出来
type Arr = ['a','b','c'] type First<T extends any[]> = T extends [unknown,...infer Rest] ? Rest : [] type a = First<Arr>
剔除尾部元素 pop
将除了第最后一个之外的其他通过ES6语法提取出来
type Arr = ['a','b','c'] type First<T extends any[]> = T extends [...infer Rest,unknown] ? Rest : [] type a = First<Arr>
infer 递归(TS -- 28)
需求:
有这么一个类型 希望通过一个 ts 工具变成 type Arr = [1, 2, 3, 4] type Arr = [4,3,2,1] 具体思路 首先使用泛型约束 约束只能传入数组类型的东西 然后从数组中提取第一个,放入新数组的末尾,反复此操作,形成递归 满足结束条件返回该类型 type Arr = [1,2,3,4] //先来一个泛型约束(对T),这是通过不断递归拿到最后一个元素(也就是问好后面的那个First)填到最前面,直到没有元素为止(三元表达式) // type ReverArr<T extends any[]> = T extends [infer First,...infer rest] ? [...ReverArr<rest>,First] : T type Arrb = ReverArr<Arr>