TS 学习心得

175 阅读14分钟

TypeScript

1.TS 类型

1.类型级别

// any 任意类型 unknown 类型
// 1.top type 顶级类型 any unknown
// 2.Object
// 3.Number String Boolean
// 4.number string boolean
// 5. 1     "1"     false
// 6.never

2.any类型和unknown类型

//1.any和unknow类型的值可以为任何类型
//2.unknown只能赋值给自身,或者是any类型
//3.unknown 自身的属性无法读取
//总结 unknown 类型比 any 类型更加安全

let any:any = "666"
let unk:unknown = "666"
let num:number = 999
// 同为顶级类型,unknown只能赋值给自身,或者是any类型
// num = nuk  //报错
num = any
console.log(num);

//unknown 自身的属性无法读取
let unkobj:unknown = {
    open:()=>{
        console.log("open");
    }
}

console.log(unkobj.open()); //报错

3.Objcet原始类型、object非原始类型 和 { }类型

// Object 原始类型
let a:Object = 123
let a1:Object = "123"
let a2:Object = []
let a3:Object = {}
let a4:Object = true
let a5:Object = ()=>123

// object 非原始类型
let b:object = 123  //错误 number非原始类型
let b1:object = "123" //错误 string非原始类型
let b2:object = []
let b3:object = {}
let b4:object = true //错误 boolean非原始类型
let b5:object = ()=>123

// {}对象类型类似于Object
let c:{} = 123 
let c1:{} = "123" 
let c2:{} = []
let c3:{} = {}
let c4:{} = true 
let c5:{} = ()=>123

4.Interface接口与对象类型

// 1.同名:使用interface属性需要和interface的参数完全一致
// 2.重合:添加同名接口,新增其它字段。使用该接口的变量,要完全实现接口里所有字段
// 3.索引签名:索引签名的内容不在进行强校验,但是接口里其它字段需要完全实现
// 4.?与readonly:?修饰的字段可以不用实现。readonly修饰的字段不可以被重写
// 5.interface继承: interface A extends B A继承B接口,实现类需要实现接口A和接口B的所有字段
// 6.interface定义函数类型:见代码第57条 23333QAQ


// 继承B接口,实现类需要实现接口A和接口B的所有字段
interface A extends B{
    name:string
    age:number
    // 索引签名,类型最好是any。如果是string,其它字段类型都必须为string
    [prop:string]:any
    // ?可选修饰符,可以不用实现
    phone?:string
    // readonly修饰的字段不可以被重写
    readonly cb:()=>boolean
}
// 重合:添加同名接口,新增参数。使用该接口的变量要完全实现接口里所有参
interface A{
    sex:string
}

//接口 B
interface B {
    bbb:string
}


// 属性需要和interface完全一致
let a:A = {
    name:"luxiaohang",
    age:24,
    sex:'男',
    // 使用索引签名
    a:1,
    b:2,

    // readonly方法被实现后,不可以再次修改
    cb:()=>{
        return true
    },

    // 实现继承接口B的字段
    bbb:"我是继承B接口的属性"
}

console.log(a.cb());//true
// 修改readonly修饰符修饰的方法
//a.cb = ()=>{} //报错

//6.定义函数类型
interface Fn{
    (name:string):number[]
}
const fn:Fn = (name:string)=>{
    return [1,2,3]
}

5.数组类型

{
//1.number[]
//2.泛型 Array<boolean>
//3.数组普通类型
//4.数组实现接口,创建对象数组
//5.多维数组类型:代码 17 行二维数组  qwq jvi
//6.函数中数组类型用法,以及arguments

// 数组实现接口
interface Arr{
    name:string
    age?:number
}
// 对象数组
const arr:Arr[] = [{name:"zhangsan"},{name:"lisi",age:18}]


// 多维数组
let arr2:number[][][] = [[],[],[]]
// 多种类型
let arr3:Array<Array<number|string|boolean>> = [[1],["2"],[true]]

// arguments
 function fn(...arg:number[]){
    let a:number[] = arg
    console.log(a);
    let b:IArguments = arguments
    console.log(b);
 }

 fn(1,2,3)
}	

6.函数类型

// 1.函数定义类型和返回值 | 箭头函数定义类型和返回值
// 2.函数默认的参数 | 函数可选参数
// 3.函数是一个对象如何定义
// 4.函数this类型
// 5.函数重载

// 定义函数类型
function add(a:number=10,b:number=20) :number{
    return a+b
}

// 定义箭头函数类型以及返回值
const multiply =(a:number,b:number):number => {
        return a * b
}

const result = multiply(4,5)
console.log(result);

//2.可选参数以及默认参数
function defaultArg(a:number = 10,b?:number):number {
    if (b) {
        return a+b
    }
    return a
}


// 3.函数是一个对象如何定义
// 函数的对象参数
interface Obj{
    name:string
    age:number
}
// 给函数传一个对象,这里接收一个Obj类型的对象
function fn(obj:Obj):object {
    return obj
}



// 4.函数this类型
interface Obj1{
    user:number[]
    add:(this:Obj1,num:number)=>void
}
// ts 可以定义this 的类型 在js中无法使用 必须是第一个参数定义 this 的类型
let obj:Obj1 = {
    user:[18,66],
    add(this:Obj1,num:number) {
        this.user.push(num)
    },
}


// 5.函数重载
const arr = [1,2,3,4]
function put(arr:number):number[]
function put(arr:number[]):number[]
function put():number[]
function put(num?:number | number[]) {
    if (typeof num == "number") {
        // 返回arr等于传进put参数的值
        return arr.filter(item=>item == num)
    }else if(Array.isArray(num)){
        // 返回合并后的数组
        arr.push(...num)
        return arr
    }else{
        return arr
    }
}

console.log(put());
console.log(put(2));
console.log(put([9,8,7,6]))

7.联合类型 | 交叉类型 | 类型断言

    // 1.联合类型  |  满足一种类型即可
    // 2.交叉类型  &  需要同时满足所有类型
    // 3.类型断言  as 将类型断言成某种类型,供编译器使用。可能会欺骗编译器

    // 联合类型
    let a:string | number = 123456
    a = '123456'

    const fn = (type:number | boolean):boolean=>{
        // 双感叹号转换为布尔类型
        return !!type
    }
     console.log(fn(1))


    //  交叉类型
    interface People{
        name:string,
        age:number
    }

    interface Man{
        sex:number
    }

    const lxh = (man:People & Man)=>{
        console.log(man);
    }

    lxh({
        name:"luxiaohang",
        age:18,
        sex:1
    })


    // 3.类型断言
    let fnAs = (num:number|string)=>{
        // num可能为number类型自身没有lenth方法,所以需要类型断言
        console.log((num as string).length);
    }
    fnAs(123456)

    // 类型断言可能出现的问题
    interface A{
        a:number
    }
    interface B{
        b:string
    }
    function asAB(num:A | B):void {
        // 通过泛型类型断言
        console.log((<B>num).b );
    }
    // 传入一个不能被断言识别的类型时,可能会得到undefine的结果
    asAB({a:123})



8.内置对象

// 内置对象
// 1.ecma模块 Date RegExp Error XMLHttpRequest
// 2.dom模块 querySelect 
// 3.bom模块 

// ecma模块
let num:Number = new Number(1)
let date:Date = new Date()
let rep:RegExp = new RegExp(/\w/)
let error:Error = new Error("err")
let xhr:XMLHttpRequest = new XMLHttpRequest()

// dom模块 HTML(元素名称) Element HTMLElement Element
let inp:HTMLInputElement = document.querySelector('input') as HTMLInputElement
// 类型不固定可以给到节点类型,往里面传入其它HTML类型
let div:NodeListOf<HTMLDivElement | HTMLElement> = document.querySelectorAll('div') 

// Bom模块
let local:Storage = localStorage
let lo:Location = location
// document.cookie 返回值是字符串
let cookie:string =document.cookie
// promise类型 返回值promise
let promise:Promise<number> = new Promise((res,rej)=>rej(1))
promise.then((res)=>{
    console.log("success",res);
})
.catch((rej)=>{
    console.log("error",rej);
})


9.class 类

1. class类型约束 implement和继承

// 1.class 用法和类型约束 implement
// 2.继承 
interface Options {
  el: string | HTMLElement;
}

interface Vueclass {
  options: Options;
  init(): void;
}

interface Vnode {
  tag: string; //div
  text?: string; //虚拟dom的key
  children?: Vnode[]; //虚拟dom集合
}

// 虚拟 dom
class Dom {
  // 创建节点方法
  createElement(el: string) {
    return document.createElement(el);
  }
  // 填充文本 text可能为空
  setText(el: HTMLElement, text: string | null) {
    el.textContent = text;
  }

  // 渲染函数
  render(data: Vnode) {
    // 渲染第一个dom
    let dom = this.createElement(data.tag);

    if (data.children && Array.isArray(data.children)) {
      // 如果children存在子节点,则递归创建dom
      data.children.forEach((item) => {
        let child = this.render(item);
        dom.appendChild(child);
      });
    } else {
      // 如果没有子节点了,直接给dom添加文本
      this.setText(dom, data.text as string);
    }

    return dom;
  }
}

// vue 实现了 Vueclass 接口 ,并且继承了虚拟dom方法
class vue extends Dom implements Vueclass {
  options: Options;
  // 传进来的一个对象 包含el
  constructor(element: Options) {
    super();
    this.options = element;
    this.init();
  }
  init(): void {
    // 虚拟dom 就是通过js 去渲染真实dom
    let data: Vnode = {
      tag: "div",
      children: [
        {
          tag: "section",
          text: "我是子节点1",
        },
        {
          tag: "section",
          text: "我是子节点2",
          children: [
            {
              tag: "section",
              text: "我是子节点2-1",
            },
          ],
        },
        {
          tag: "section",
          text: "我是子节点3",
        },
      ],
    };

    // 如果是string则手动获取dom节点,不是string而是dom直接使用该dom
    let app =
      typeof this.options.el == "string"
        ? document.querySelector(this.options.el)
        : this.options.el;

    // 渲染一个dom
    app?.appendChild(this.render(data));
  }
}

new vue({
  el: "#div",
});



2.class 修饰符 readonly public

// 1.class 成员属性 readonly public protected private
// 成员属性默认为 public 可以给子类、外部去调用

// 2.super() 原理是父类的 prototype.constructor.call
// 可以给父类的constructor中传参 

// 3.静态方法 static 外部可以不用实例化类,直接调用类成员方法
// static 方法内只能使用static修饰的方法和属性。无法使用类中其它方法和属性

// 4. get() set() 
class A {
    readonly red:number = 1;
    constructor(str?:string){
        // 只能从在内部去调用pri
        // this.pri() 
        
        // 打印实例化传进来的参数
        console.log(str);
    }

    protected prot(){
        console.log("我是被protected修饰的内部方法");
    }

    private pri(){
        console.log("我是被private修饰的内部方法");
    }
}

const a =  new A()
// a.red = 2 // 报错 readonly 修饰的成员属性无法被修改

// a.print() //报错 private修饰的成员属性,无法被外部和子类调用
// a.prot() //报错 protected修饰的成员属性,只能在类中和子类去调用

// 2.super()
class AA extends A{
    a = 6
    static b = "123"
    constructor(){
        // 调用父类构造器
        super("我是子类参数,调用了父类构造器")
    }

    prot(){
        // 可以通过super.属性名 直接使用父类属性或方法
        super.prot()
    }

	//3.static
    static printa(){
        // static修饰方法无法调用非 static修饰成员
        // let a = this.a //报错
        // let b = this.b
        console.log('a');
    }
}

const aa = new AA()
AA.printa()

// 4. get()/set()
class getset{
    // 私有成员属性,通过get\set方法暴露出去,可以更好的保护属性安全
   private _a:any
    get a(){
        return this._a
    }

    set a(a:any){
        this._a = a
    }
}

const result = new getset()
// 通过get a获取私有变量 _a 的值
console.log("get a =", result.a)
// 通过set a修改私有变量 _a 的值
console.log("set a =", result.a = 999)
// 通过get a获取私有变量 _a 的值
console.log("get a =", result.a)


3.abstract抽象类

// 抽象类
// abstract 所定义的是抽象类,抽象类中可以有抽象方法也可以有普通方法
// 抽象类不能被实例化
// 抽象类的抽象成员属性必须被实现

abstract class Animal{
   name?:string
    abstract behavior:any
    constructor(name?:string,behavior?:string){
        this.name = name
    }

    getName():string | undefined{
        return this.name
    }

    abstract init(name:string):void
}

class Cat extends Animal {
    behavior:string
    constructor(){
        super()
        this.behavior = "小猫喵喵叫"

    }
    //必须实现父类的抽象方法
    init(name:string){
    }
    
    setName(name:string){
        this.name = name
    }
}

const cat = new Cat()
cat.setName("张三")
//继承抽象类后,可以直接调用父类非抽象方法
console.log(cat.getName()) //打印: 张三
console.log(cat);
console.log(Cat);

10.tuples 元组

//1.元组类型
//2.元组联合类型
//3.获取元组类型

// 元组类型
let arr:[number,boolean] = [1,true]
arr.push(6)

// 将元组设置为完全只读,不可修改其数值和存储地址
let arr2:readonly[number,boolean] = [2,false]
arr2 = [9,false] 
const arr3 = arr2
// arr3 = [14,false]  //报错
// arr3.push(666)  //报错

// 元组结合联合类型
const arr4:readonly [x:number,y?:boolean | string] = [1,"123"]
console.log(arr,arr2);

// 获取元组类型
type tuplesType = typeof arr4[0]
type tuplesType2 = typeof arr4[1]
type tuplesType3 = typeof arr4["length"]

11.枚举enum

// 枚举
// 普通函数区分颜色
const fn = (type:any)=>{
    if(type ==0){
        return "red"
    }
    if(type == 1){
        return "green"
    }
}
console.log(fn(0))

// 使用枚举
enum Color{
    red = 0,
    green= 1,
}

class opColor{
    constructor(color:any){
        // 枚举反向映射
        if (color == Color.red) {
                console.log(`key = ${Color[color]}`,`value = ${Color.red}` ) // 输出 key = red,value = 0
        }
        if (color == Color.green) {
            console.log(Color[color])
        }
    }
}

let color =  new opColor(1)

12.类型推断 | 类型别名 Type

// 1.类型别名 type
// 2.type 和 interface 区别
// 3.type 联合类型

//1.type
type a = number[] //给定类型
let a:a = [666]

interface A{
    name:string
}


//2. type 与 interface 区别 
// interface 可以写成联合类型
interface B extends A {
}
// interface 遇到同名会进行合并
interface A{
    age:number
}

type b = string
// type b = number //type同名会报错

//3.type 的联合类型
type c = number[] | string
let c:c = [1]


// 4.type高级用法
// extends 在 type中是包含的意思. extends左侧类型会作为右侧类型的子类型
// 左侧 extends 右侧
type num = 1 extends number? 1 : 0  // num = 1

13.never 类型

// 1. never 永远无法达到的状态
// 2. never 的使用场景
// 3. never 在联合类型中会被忽略

type a = string&number //该类型被推断为 never ,因为变量无法同时成为 number 和 string

// 2.该函数使用就会报错,所以返回值给到 never 更为合适
function err():never {
        throw new Error("err")
}

// 死循环同样可以给到 never 返回值,因为它无法返回
function DeathLoop():never {
    while(true){
    }
}

type Color = "red"| "green"|"blue"//|"yellow"    //新增属性 yellow 会直接报错
function color( col:Color) {
    switch (col) {
    case "red":
        break;
    case "green":
        break;
    case "blue":
        break;
            // 当switch定义完成后,如果type中新增类型没有被case。就会去调用default从而报错
    default:
        const error:never = col
        break
    }
}

// 3. never 联合类型中会被忽略
type num = number | string | never //这里num类型为 number | string , never被忽略掉了

14.Symbol 类型 | ES6 yield 生成器 iterator 迭代器

1.Symbol

// 1. symbol 是唯一的,相同值不相等
// 2. symbol.for()  for全局symbol是否注册过这个key,如果有直接拿过去用,不会重新创建.没有则创建
// 3. Symbol 解决key重复的问题
// 4. 遍历 Symbol

// 1. symbol
let a1:symbol = Symbol(1)
let a2:symbol = Symbol(1)
console.log(a1 == a2); //打印 false

// 2.symbol.for()使用,()里必须是一个string类型
console.log(Symbol.for("666") === Symbol.for("666")); // true

// 3.Symbol 解决key相同的问题
const obj = {
    a:"zhangsan",
    // 这里使用了 Symbol 类型,[a1] : [a2] key值相等但是不会覆盖
    [a1]:"123",
    [a2]:"456"
}
// 使用 Symbol 类型后 ,[a1] : [a2] key值相等但是不会覆盖
console.log(obj); //打印:{ a: 'zhangsan', [Symbol(1)]: '123', [Symbol(1)]: '456' }


// 4. Symbol 的遍历
// for in 遍历返回自身可枚举属性、原型属性,返回值:Key 
for (const key in obj) {
   console.log(key); //打印: a
}
// Object.keys 遍历返回自身可枚举属性。不包括不可枚举属性,原型属性,Symbol。返回值:数组
console.log(Object.keys(obj)) //打印 ['a']

// Object.getOwnPropertyNames 获取自身所有属性,包括不可枚举属性。不包括Symbol,不包括原型。返回值:数组
console.log(Object.getOwnPropertyNames(obj)) //打印 ['a']

// Object.getOwnPropertySymbols 遍历对象里所有的 Symbol 类型数据。返回值:数组
console.log(Object.getOwnPropertySymbols(obj));// 打印 [ Symbol(1), Symbol(1) ]

//Reflect.ownKeys 完美返回对象里所有属性和 Symbol 属性的方法,不包括原型。返回值:数组
console.log(Reflect.ownKeys(obj)) // 打印 ['a', Symbol(1), Symbol(1) ]

2.yield 生成器 iterator 迭代器 set map 解构赋值 For Of

// 1.生成器
// 2.迭代器
// 3.set map
// 4.iterator
// 5.iterator 语法糖 for of
// 6.解构赋值 底层调用iterator
// 7.forof支持对象,重写对象的Symbol.iterator

// 1.生成器 yield 定义迭代的内容
function* ToMoon() {
    yield Promise.resolve("25% in the earth")
    yield "50%"
    yield "75%"
    yield "100% to the Moon"
}

// 通过 next() 进行迭代
// 如果返回值对象里 done 变成 true 。说明该方法无法在进行迭代了
const go = ToMoon()
console.log(go.next()); // { value: Promise { '25% in the earth' }, done: false }
console.log(go.next()); // { value: '50%', done: false }
console.log(go.next()); // { value: '75%', done: false }
console.log(go.next()); // { value: '100% to the Moon', done: false }
console.log(go.next()); // { value: undefined, done: true }

// 2.迭代器 任何伪数组都拥有迭代器 iterator
// 3.Set Map
let set:Set<number> = new Set([1,1,1,2,3,3,5]) //去重
console.log(set); // [1,2,3,5]

let map:Map<string,number> = new Map() //集合
map.set("m1", 123)

function args() {
    console.log(arguments); //伪数组
}

// document.querySelectorAll(".div")//伪数组

// 封装迭代方法
const each = (val:any)=>{
    // 迭代器使用 
     let iter:any = val[Symbol.iterator]()
     let next:any = {done:false}
    // 计数器
     let num = 0
     while (!next.done) {
        next = iter.next()
        num++
        // done值为false继续遍历
        if (!next.done) {
          // 迭代器遍历
            console.log(`each遍历第:${num} 次`, next.value)
        }
     }
}
each(set) 
/* 
输出
each遍历第:1 次 1
each遍历第:2 次 2
each遍历第:3 次 3
each遍历第:4 次 5
*/

// 5.迭代器的语法糖 for of 
// for of 不能用于对象类型,对象类型上没有[Symbol.iterator]()方法
// for (const item of {name:"张三"}) {} //报错 
 for (const item of [{name:"张三"},{name:"李四"}]) {
    console.log(item);
    // 输出
    // { name: '张三' }
    // { name: '李四' }
 }


//  6.结构赋值,底层原理也是调用iterator
let [a,b] = [1,2];
console.log(a,b);//a = 1, b = 2
[a,b] = [b,a]
console.log(a,b);//a = 2, b = 1
let c = [1,2,3]
let copy = [...c] 
console.log(copy); // copy = [1,2,3]

//7.让对象支持for of和数组解构,重写 [Symbol.iterator]()
const obj:any = {
    o:1,
    o2:2,
    // 对象结构赋值或者调用 for of 会触发该方法
    [Symbol.iterator]:function(){

        // 获取当前对象的 key,得到 obj 的数组
        let keys =Reflect.ownKeys(this)
        let index = 0
        return{
            next(){
                // index 大于 keys.length 循环结束
             if(index>= keys.length){
                return {
                    done:true
                }
              }else{
                return{
                    done:false,
                    value:{
                        key:keys[index],
                        // 每次获取keys后自增
                        value:obj[keys[index++]]
                    }
                }
              }
           }
        }
    }
}

 let newObj = [...obj]
 console.log("newObj = ",newObj);
/* 
    打印
    newObj =  {
    o: 1,
    o2: 2,
    [Symbol(Symbol.iterator)]: [Function: [Symbol.iterator]]
    }
*/
// 使用 for of 迭代
for (const value of obj) {
    console.log(value);
}
/* 
    打印
    { key: 'o', value: 1 }
    { key: 'o2', value: 2 }
    { key: Symbol(Symbol.iterator), value: [Function: [Symbol.iterator]] }
*/

2.泛型 generic

1.泛型使用

// 1.泛型
// 2.interface 泛型
// 3.type 泛型
// 4.函数的泛型

//1.需要类似类型处理不同功能时需要再次创建函数,这时可以使用泛型
function lu(a:number,b:number):number[]{
    return [a,b]
}
function lu1(a:number,b:string,c:any):(number|string)[]{
    return [a,b]
}

// 泛型 由接收类型设置类型
function fx<T,K>(a:T,b:K):Array<T | K>{
    return [a,b]
}
fx("6",1)
fx(false,true )

//2. interface 设置泛型
interface type<T>{
    name:T
}
// 使用interface 设置泛型
let data:type<string | number | Array<string>>  = {
    // name:"123"
    // name:123
    name:["张三"]
}

// 3. type 设置泛型
type classGeneric<T> = {
// 设置泛型t
    name:T
}
const typeGeneric:classGeneric<string>= {name:"张三"}

// 4.函数的泛型
// 可以给泛型默认类型
function sum<T = number,K = number>(a:T,b:K ):Array<T|K> {
    return  [a,b]
}

sum(true,"999") 
// 类型 function sum<boolean, string>(a: boolean, b: string): (string | boolean)[]  
// 返回值 [true,"999"]

2.泛型使用场景 ajax封装

const axios = {
    get<T>(url:string):Promise<T>{
        //这里返回了 Promise<unknown>
        return new Promise((res,rej)=>{
            let xhr:XMLHttpRequest = new XMLHttpRequest()
            xhr.open("GET", url)

            xhr.onreadystatechange = ()=>{
                if (xhr.readyState == 4 && xhr.status == 200) {
                    res(JSON.parse(xhr.responseText))
                }
            }
            xhr.send()
        })
    }
}

interface Data{
    name:string,
    age:number
}

axios.get<Data>("./16_index.json").then((res)=>{
    console.log(res.name,res.age);    
})

3.泛型约束 keyof

// 1.泛型约束 extends
// 2.interface约束泛型
// 3.keyof  将 对象类型(类型不是值) 推断成联合类型
// 4.function泛型约束
// 5.keyof遍历

// 1. 在类型后面跟一个 extends 在跟一个约束类型
function add<T extends number>(a:T,b:T) {
    return a + b
}
// console.log(add("123","456")) // 使用string类型传参,报错

// 2.interface约束泛型
interface Len{
    length:number
}
// 传进来的参数需要有 lengts 属性,length值为number
function arr<T extends Len>(data:T) {
    return data.length
}

arr([1,2,3,4,5,6])  //返回值 6
arr('123')          //返回值 3
// arr({a:1,b:2})      //报错 object 没有 length 属性

// 3. keyof 
let obj = {
    name:"张三",
    age:22
}

// 对象类型推断成联合类型,方便泛型约束
type Key = keyof typeof obj // Key = "name" | "age"

// 4.function泛型约束,获取value,保证类型安全  
// K = “类型” | "类型"
function fn<T extends object,K extends keyof T>(obj:T,key:K) {
        return obj[key]
}

// fn(obj,"a") //报错 obj中不存在"a"属性
fn(obj, "name")


// 5.通过泛型约束 extends 和 keyof 将类型变成可选类型
interface Data{
    name:string,
    age:number,
    number:number
}

// 创建一个类型,将上面类型变为可选
// 传进来的类型需要是一个object
type Option<T extends Object> = {
    // for in 语句相当于 let key in obj
     [Key in keyof T]?:T[Key]
}

type Data2 = Option<Data> 
/* 
    Data2 类型变成了可选 
    type Data2 = {
    name?: string | undefined;
    age?: number | undefined;
    number?: number | undefined;
    }
*/


3.namespace 命名空间

// 1.命名空间 namespace
// 2.多个同名命名空间

// 1.namespace内部相当于函数作用域,底层用的自执行函数。
namespace A{
    // 不使用export导出,外部无法读取值
    export const a = 1
    export var b = 6
    export namespace B{
        export const a = "123"
        // namespace空间的内容会先执行
        console.log("我是b");
    }
}
console.log(A.a,A.B.a);


// 2.多个同名命名空间使用的是同一个命名空间
namespace A{
    // export const a = '666' //报错,无法重复声明const变量
    export var b = 1
}
console.log(A.b);

4.三斜杠指令

// 1.三斜杠指令,类似inport

// 1.三斜杠指令
///<reference path="./18_namespace.ts">
/* 
    文件 ./18_namespace.ts 内容:
    namespace A{
        export const a = 999
    }
*/

namespace A{
    export const c = 999
}

console.log(A); //{a:999,c:999}

5.声明文件 .d.ts


// 1. d.ts 文件
// 2. 导入外部插件

//  1.xml在ts中没有声明 d.ts 文件,这里import导入时会报错
// import xml from 'XMLhttpRequest' //报错

// 2. 自定义声明文件
import express from "express" //express.d.ts声明后不再报错
const app = express()
const router = express.Router()

app.use("/",router)

router.get('/api',(req:any,res:any)=>{
    res.json({
        code:200
    })
})

app.listen(8080,()=>{
    console.log("服务器启动 http://localhost:8080/api");
})

/******************下面是 ../typings/express.d.ts 文件内容********************/

// express 的 d.ts 声明文件
declare module 'express'{
    interface Express{
    // const app = express() 会调用该类型
        ():APP
        Router():Router
    }

    interface APP{
        use(path:string,router:any):void 
        // 监听 回调可选
        listen(port:number,cb?:()=>void)
    }

    interface Router{
        get(path:string,router:any):void
    }

    const express:Express

    // 导出express
    export default express
}


6.mixin 混入

1.Object.assign

// 1.Object.assign 

interface Name{
    name:string
}

interface Age{
    age:number
}

interface Sex{
    sex:string    
}
const a:Name = {name:'张三'}
const b:Age = {age:18}
const c:Sex = {sex:"男"}

// 使用Object.assign,类型变成了交叉类型
const obj = Object.assign(a,b,c)//const obj: Name & Age & Sex


2. mixin 重组对象

// 1.mixin


class A{
    name:string
    getname():string{
        return this.name
    }
}

class B{
    age:number
    getAge():number{
        return this.age
    }
}

class C implements A,B {
    name:string = "luxiaohang"
    age:number = 18
    getname:()=>string
    getAge:()=>number
}

mixins(C,[A,B])
function mixins(curCls:any,itemCls:any[]) {
    itemCls.forEach(item=>{
        // getOwnPropertyNames 遍历对象身上所有可枚举和不可枚举的属性
        Object.getOwnPropertyNames(item.prototype).forEach(name=>{
            // C对象通过遍历 A,B 对象的原型,拿到原型上的方法挂载到 c 类的原型上
            curCls.prototype[name] = item.prototype[name]
        })
    })
}

// 通过 mixins 
let classC = new C()
// c类成功拿到了 B 类和 a 类的方法
console.log(classC.getname()); //打印 lxh

7.装饰器 Decorator

// 1.类装饰器  ClassDecorator
// 2.方法装饰器 MethodDecorator(参数1:原型对象,参数2:函数名称,参数3:PropertyDescriptor) 
// 3.参数装饰器 ParameterDecorator 参数修饰器比方法修饰器更早执行
// 4.属性装饰器 PropertyDecorator 
// 5.装饰器工厂
// 6.import "reflect-metadata"
// 7.axios
// 8.Reflect.metadata 元数据
/* 
    元数据的添加 Reflect.metadata
    Reflect.metadata(参数1:对象上添加的key,参数2:对象添加的值,参数3:添加元数据的类) //给参数3的类,添加 key:value 元数据
    Reflect.metadata(a,"123",obj)  // obj{a:"123"}

    Reflect.metadata(参数1:对象上添加的key,参数2:对象添加的值,参数3:添加元数据的类,参数4:添加元数据的属性)  //给参数3的类的参数4属性,添加 key:value 元数据
    Reflect.metadata(a,"123",obj,a)  // obj{ a:{ a:"123"}}

    元数据获取 Reflect.getMethod
    Reflect.getMethod(参数1:对象上的key,参数2:元数据的类) //获取 参数2类 挂载的元数据
    Reflect.getMethod(参数1:对象上的key,参数2:元数据的类,参数3:元数据参数2类的属性) //获取 参数2类.参数3 的元数据
*/

import axios from 'axios';
import 'reflect-metadata'

// 1.类装饰器  ClassDecorator
const create:ClassDecorator = (target) =>{
    target.prototype.lu = "luxiaohang"
    target.prototype.fn = ()=>{
        console.log("桀桀桀");
    }
}
// 有了装饰器后,可以不破坏原本class结构去添加属性
@create 
class People{
}
const man = new People() as any
console.log(man.lu);

// 通过函数科里化,用装饰器给工厂直接传参
const create2  = (name:string)=>{
    const fn:ClassDecorator = (target:any)=>{
        // 类原型上挂载属性
        target.prototype.name = name

        target.prototype.fn =()=>{
            console.log('base2 装饰器');
        }
    }
    return fn
}

@create2("lxh")
class People2 {
}
// 实例化装饰器对象,需要类型断言因为类型默认为:ClassDecorator
const man2 = new People2() as any
console.log(man2.name);



//===================下面方法在一个类中使用=======================
//类装饰器
const Base = (name:string)=>{
    // target 传进来的是 class 
    const fn:ClassDecorator = (target)=>{
        target.prototype.name = "张三干掉" + name
        target.prototype.fn = ()=>{
            console.log("我是base fn()");
        }
    }
    return fn
}


// 2.方法装饰器
const get = (url:string)=>{
    /* 
    // 装饰器形参
   fn:MethodDecorator =(
        target:"获得class中所有方法", // { getList: [Function (anonymous)], create: [Function (anonymous)] }
        propertyKey:"获得当前方法名", //  getList
        descriptor:"获得一个对象 .value给方法回传的内容"{value: [Function (anonymous)],writable: true,enumerable: true,configurable: true}
    ) 
    */
    //descriptor需要给定PropertyDescriptor 属性,不然参数不可用
    const fn:MethodDecorator =(target,propertyKey,descriptor:PropertyDescriptor)=>{
        // 获取到字符串 list
        const key = Reflect.getMetadata('getList', target)
            axios.get(url)
            .then(res => {
                // 通过 descriptor 回传成功的回调.返回元数据 value
            
                descriptor.value(res.data[key])
            })
    }
    return fn
}

// 3.参数装饰器
const List = ()=>{
    // 参数修饰器需要加类型 ParameterDecorator 。参数修饰器
    const fn:ParameterDecorator = (target,key,index)=>{
        //设置一个 getList 值为 list 供元数据获取
        Reflect.defineMetadata("getList","list",target)
    }
    return fn
}

// 4.属性修饰器
const Name:PropertyDecorator = (target,propertyKey)=>{
        console.log(target, propertyKey);   //{},name
}

@Base('LXH')
class Http{
    // 4.属性修饰器
    @Name
    name:string
    constructor(){
        this.name = "张三"
    }

    // 2.指定一个方法修饰器
    @get('http://localhost:8080')
    // 3.指定一个参数修饰器
    getList(@List() data:any){
        console.log("我收到了后端请求",data);
    }
    create(){
    }
}



8.localstorage本地存储

1.结构目录

//结构目录
|--src
	|--enum
		|--index.ts
	|--type
		|--index.ts
	|--index.ts

2.enum文件夹

//字典 expire 过期时间 key,permanent永久不过期
export enum Dictionaries{
    permanent = "permanent",
    //签名
    expire = "__expire__"
}

3.type文件夹

import {Dictionaries} from '../enum';
export type Key = string
// expire localStorage过期时间类型
export type Expire = Dictionaries.permanent | number //永久 或者 时间戳
// Data 类型
export interface Data<T>{
    value:T,
    [Dictionaries.expire]:Expire
}
// Value 类型
export interface Result<T>{
    message:string
    value:T|null
}
export interface StorageCls{
    get:<T>(key:Key)=>void
    set:<T>(key:Key,value:T,expire:Expire)=>void
    remove:(key:Key)=>void
    clear:()=>void
}

4.index.ts

import {StorageCls,Key,Expire,Data,Result} from './type'
import {Dictionaries} from "./enum"
export class Storage implements StorageCls{
    // 默认存储时间为永久
    set<T>(key:Key,value:T,expire:Expire = Dictionaries.permanent){
            const data:Data<T> = {
                value,//用户value
                [Dictionaries.expire]:expire //过期时间
            }
            // 存放用户数据
            localStorage.setItem(key, JSON.stringify(data))
    }
    // 取出数据,返回值 T 或者 null
    get<T>(key:Key):Result<T | null>{
        const result =  localStorage.getItem(key)
        if (result) {
            // 取到数据存放为JSON
            const data:Data<T> = JSON.parse(result)
            const now = new Date().getTime()

            // 如果过期时间小于当前时间:返回已过期
            if(typeof data[Dictionaries.expire] == "number" && data[Dictionaries.expire]<now){
                // 清除key
                this.remove(key)
                return {
                    message:`您的 ${key}已过期`,
                    value:null
                 }
            }else{
                return{
                    message:"成功",
                    value:data.value
                }
            }
        }else{
            return {
                message:"无效值",
                value:null
            }
        }
    }
    // 移除指定的 localStorage
    remove(key:Key){
        localStorage.removeItem(key)
    }
    // 清空 localStorage
    clear(){
        localStorage.clear()
    }
}

   const save = new Storage();
      save.set("a", 123, new Date().getTime() + 10000);
      setInterval(() => {
        console.log(save.get("a"));
      }, 500);

9.Dispatch发布订阅

// 订阅发布模式
interface Events{
    on:(name:string,fn:Function)=>void
    emit:(name:string,...args:Array<any>)=>void
    off:(name:string)=>void
    once:(name:string,fn:Function)=>void
}
interface List{
    [key:string]:Array<Function>
}

class Dispatch implements Events {
   list:List
   constructor(){
    this.list = {}
   }
//1.订阅
    //往list对象传入回调,emit发布时执行所有回调方法
   on(name:string,cb:Function){
    const callback = this.list[name] || []
    callback.push(cb)
    this.list[name] = callback
   }

    // 2.发布 
    //    ...args向on传的参数
   emit(name:string,...args:Array<any>){
    // 获取当前class的list内容
    let eventName= this.list[name] 
            // 判断 list.name 是否存在
            if (eventName) {
                // 存在
                eventName.forEach(item=> {
                     item.apply(this,args)
                });
            }else{
                console.error(`名称输入有误${name}`)
            }
   }

   off(name:string){
    const list = this.list[name]
    console.log(list);
    
    if (list.length) {
        this.list[name] = []
        console.log("删除成功");
    } else{
        console.error(`删除失败,该对象不存在`);
    }
   }

// 向list中传入一个one的回调函数,执行后删除该函数
   once(name:string,fn:Function){
    let one = (...args:Array<any>)=>{
        fn.apply(this,args)
        this.off(name)
    }
    this.on(name, one)
   }
}

const list = new Dispatch()
list.on("name",(...args:Array<any>)=>{
    console.log("添加了name 1",args);
})
list.on("name",(...args:Array<any>)=>{
    console.log("添加了name 2",args);
})
list.emit("name","我是通过emit添加的内容",{name:"luxiaohang"})
list.off("name")
list.off("name")

10.proxy代理

1.proxy

// 1.proxy
// 2.proxy 拦截

// proxy 代理 13 个方法
// Reflect 代理 13 个方法

let person = {name:"陆小杭",age:24}
person.name //取值
person.name = 'xxx' //赋值

let personProxy = new Proxy(person,{
    // 取值时调用
    get(target, p, receiver) {
        
    },
    // 赋值时调用
    // person name 
    set(target,key,value,receiver){

        return true
    },
    // 拦截函数的调用
    apply(){

    },
    // 拦截 in 操作符
    has(){
        return true
    }, 
    // 拦截forin
    ownKeys(){
        return []
    },
    // 拦截 new 操作符
    construct(){
        return {}
    }
    // 拦截删除操作
 /*     deleteProperty(target,p){
    }  */
})


// 2.proxy 拦截
let person = {name:"lxh",age:24}
let personProxy = new Proxy(person,{
    get(target,key,receiver){
        if (target.age >18) {
            return Reflect.get(target,key,receiver)
        }else{
            return "无法获取到小于 18 岁的内容 "
        }
    }
})

2.观察者模式



// 观察者模式
const list:Set<Function> = new Set()
const autorun = (cb:Function)=>{
    if (!list.has(cb)) {
        // 没有就添加
        list.add(cb)
    }
}

const observable = <T extends object>(params:T) =>{
    return new Proxy(params,{
        set(target, p, value, receiver) {
            const result = Reflect.set(target, p, value, receiver)
            // 这里调用了autorun()这个方法
            list.forEach(fn => fn())
            return result
        },    
    })
}

const ObserverProxy = observable({name:'qq',attr:"一只企鹅"})
autorun(()=>{
    console.log("有变化了");
})
ObserverProxy.attr = "一匹马"
ObserverProxy.attr = "666"

11.协变与逆变

// 1.协变
// 2.逆变

// 鸭子类型 十分相似
interface A{
    name:string
    age:number
}

interface B{
    name:string
    age:number
    sex:string
}

let a:A = {
    name:"老墨吃鱼",
    age:33
}

let b:B = {
    name:"高启强",
    age:23,
    sex:"男"
}

// 1.协变 对象多可以传给少的
a = b

// 2.逆变 逆变是安全的
let fna =(params:A)=>{
}
let fnb =(params:B)=>{
}
fnb = fna 


12.weakmap | weakset

// 1.weakmap 是一个弱引用,不被计入垃圾回收机制。
// weakmap指向的对象没有目标引用时,weakmap也将取消指向
// 2.weakset 也是一个弱引用,不会计入垃圾回收机制。

let obj:any = {     //引用次数 1
    name:"张三"
}
let obj2:any  = obj      //引用次数 2
// weakmap key必须是对象类型
const weakM:WeakMap<object,any> = new WeakMap()   //引用次数 2,不计入引用次数
weakM.set(obj, {okk:"123456"})
console.log(weakM.get(obj)) //打印 {okk:"123456"}

obj = null
obj2 = {}

console.log(weakM.get(obj))//没有引用了 打印 undefine

// weakset 内容也需要是object类型
new WeakSet([])


13.Partial & Pick

type Person = {
    name:string
    age:number
    text:string
}

// Pick 将类型拆分出来
type p = Pick<Person,'age'|'name'>
// 使用 keyof 语法
function fn<T extends Person,K extends keyof T>(person:T,p:K){
    console.log(person[p]);
}

fn({name:"张三",age:18,text:"111"},"name")

type key = "name" | "age" | "sex"
type Par<T> = {
    [P in keyof T]?:T[P];
}
type keyof = Par<Person>


14. readonly & record

// 1.readonly
// 2.record
type People = "name"|"age"|"sex"
type partial<T> = {
  readonly [P in keyof T]:T[P]
}
const obj = {
    name:"zhangsan",
    age:18,
    sex:"nv"
}
function fn<T>(params:partial<T>) {
    // console.log(params);
    return params
}
// 通过 readonly 修饰后函数只能读取不能修改
const cb = fn(obj)
// cb.name = 1  //报错.name 只读

// 2.record
type Rec<K extends keyof any,T>={
    [P in K] :T
}
type person ={
    name:string,
    age:number,
    sex:string
}
type Str = "A"|"B"|"C"
type newperson = Rec<Str,person>
/* 
    newperson的类型
    type newperson = {
    A: person;
    B: person;
    C: person;
}
*/



15.infer 推断

//1. T[number] T[any] 数组元数据 返回数组元素的类型 
//2. <infer T> 返回数组元素的类型 
//3. infer 数组元素的类型 
//4. infer 获取数组中的元素


// 定义一个类型,如果是数组类型 就返回数组元素的类型 
// 否则就传入什么类型 返回什么类型
type TYPE<T> = T extends Array<any> ? T[number] : T
type A = TYPE<(string | number)[]> 
// 这里返回值为啥是  string | Number 呀
// A 不应该是 (string | number)[] 吗?

// type A = (string | Number)[]
type B = TYPE<boolean>
type t<T> = number[]

// 2.infer:可以返回一个数组元素的类型.这里的 U 不是泛型只是一个占位符
// <infer U>  u是数组元素的类型
type TYPE2<T> = T extends Array<infer U>? U : T
type obj2 =TYPE2<(string|number|Function)[]>; //obj2 类型:string|number|Function
type obj2type = TYPE2<obj2>

// 3.infer 数组元素的类型 
type TYPE3<T> = T extends Array<infer U>? U : never
// 元组
type arr = [number,string]
type uni = TYPE3<arr> // uni 类型 : string | number
type err = TYPE3<boolean>// err 类型 : never


// 4.infer 获取数组中的元素
type arr2 = [[],'a',"b","c"]
// 判断数组是否存在 3 个参数,没有返回 []
type TYPE4<T extends any[]> = T extends [infer one,infer tow,infer three] ? tow : []
type getType4 = TYPE4<arr2>
// 获取数组中最第一个数据类型
type TYPE5<T extends any[]> = T extends [infer one,...any[]] ?  one: any
type getType5 = TYPE5<arr2> //类型 []
// 获取数组中最后一个的数据类型
type TYPE6<T extends any[]> = T extends [...any[],infer Last] ?  Last: any
type getType6 = TYPE6<arr2> //类型 c
// 获取数组中多个数据类型
type TYPE7<T extends any[]> = T extends [...infer first,infer Last] ? first: []
type getType7 = TYPE7<arr2> //类型 [[],'a','b']
// 放弃数组中的第一个数据类型
type TYPE8<T extends any[]> = T extends [unknown,...infer Last]?Last:[]
type getType8 = TYPE8<arr2> //类型 ['a','b',"c"]

// infer 递归
type Arr = [1,2,3,4,5,6]

type ReversArr<T> = T extends [infer first,...infer last] ? [...ReversArr<last>,first]:T

type recursion = ReversArr<Arr>


TS 在 VUE 中的使用

1.ts+ref

//定义简单数据类型
//需要注意,指定了一个泛型参数但没有给出初始值,那么最后得到的就将是一个包含 undefined 的联合类型:
// 推导得到的类型:Ref<boolean | undefined>
const show = ref<boolean>(false);
或者
const show:Ref<string> = ref(false);
//简单数据类型赋值
const handleShow = ():void=>{
    show.value = true
}
//---------------------------------------------------------------------------------------
//定义复杂数据类型
interface ArrListType {
    name:string
    age:number
    des:string
}
type ObjListType = partial<ArrListType>

const arrList = ref<ArrListType[]>([])
const objData = ref<ObjListType>({})
//复杂数据类型赋值
const handleArr = ()=>{
    //mock数据
    const res = [{name:'x',age:10,des:'x'},{name:'y',age:11,des:'y'}]
    arrList.value = res
}
const handleObj = ()=>{
    //mock数据
    const res = {name:'x',age:10,des:'x'}
    arrList.value = res
}

2.获取组件 ref 实例+ts

使用 vue3 和 ts 时,为了获取 组件 ref 实例,就需要在 ref 函数的泛型中指定类型。如何获取组件的类型呢?vue 官方文档中 TypeScript 支持里已经告诉我们了一个获取组件类型的方法,InstanceType<typeof 组件名称>,使用方式如下:

//为了获取组件的类型,我们首先需要通过 typeof 得到其类型,再使用 TypeScript 内置的 InstanceType 工具类型来获取其实例类型:
const $userForm = ref<InstanceType<typeof userForm>|null>(null);

3.ref实例+ts

//子组件
<NewsDialog ref="news" @refreshData="getList()"></NewsDialog>

<script>
    //导入子组件
    import NewsDialog from './components/NewsDialog.vue'
    //获取子组件实例
    const news = ref<InstanceType<typeof NewsDialog>>()            
    //打开消息弹窗
    const openNewsDialog = (): void => {
      news.value?.showDialog()
    }

    //子组件暴露方法
    defineExpose({
      showDialog,
    });

</script>

处理原生 DOM 事件时 + ts //input标签

const input = ref<HTMLElement | null>(null); //获取焦点 (input.value as HTMLInputElement).focus(); ts结合prop使用(父传子) 注意:传递给 defineProps 的泛型参数本身不能是一个导入的类型:

//vue3+js的写法 const props = defineProps({ id:{ type:Number, default:0 } arr{ type:Array default:()=>[] } }) //vue3+ts写法 interface PropType = { id:number arr:string[] } const props = defineProps() //props 声明默认值 通过 withDefaults 编译器宏解决 推荐 const props = withDefaults(defineProps(), { id: 1, arr: () => ['one', 'two'] }) //下面的默认值写法暂时还处在实验性阶段,了解为主 const { id, arr=[] } = defineProps() ts结合emit使用(子传父) //vue3+js的写法 const emits = defineEmits(['change', 'update']) //vue3+ts写法 const emits = defineEmits<{ (e:'change'):void, (e:'update',value:string):void }>() ts+computed computed() 会自动从其计算函数的返回值上推导出类型:

const count = ref(0) // 推导得到的类型:ComputedRef const double = computed(() => count.value * 2) // => TS Error: Property 'split' does not exist on type 'number' const result = double.value.split('')

//当然,也可以通过泛型参数显式指定类型: const double = computed(() => { // 若返回值不是 number 类型则会报错 }) provide / inject + ts import { provide, inject } from 'vue' import type { InjectionKey } from 'vue' //建议将key抽出去,方便复用 const key = Symbol() as InjectionKey

provide(key, 'foo') // 若提供的是非字符串值会导致错误

const foo = inject(key) // foo 的类型:string | undefined ts+pinia 在pinia里已经去掉了mutation

首先要安装全局依赖npm i pinia

//封装使用的pinia 在store/index.ts里 //导入defineStore import { defineStore } from 'pinia' //导入接口 import { getBoxConfigApi } from '@/api/GuardBox' //定义数据接口 export interface giftInfoType { use_gift_id: number count: number level: number gift: { id: number name: string } } //定义数据接口 interface AllConfigType { all_config: Partial<{ title_img: string date_range: string[] year_select: string[] ttl: string constellation: string user_level: string }> gift_info: giftInfoType[] } //使用pinia export const useStore = defineStore('main', { state: (): AllConfigType => ({ all_config: {}, gift_info: [], }), actions: { async getConfig(level = 1) { const { data } = await getBoxConfigApi({ level }) this.all_config = data.all_config this.gift_info = data.gift_info return data }, }, })

//引用pinia 在homePage.vue里

<script setup lang="ts">
    
import { useStore } from '@/store'
const store = useStore()
// 礼物配置信息
const giftConfigInfo = ref<giftInfoType[]>([])
// 获取礼物信息
const getGiftInfo = async (): Promise<void> => {
  const { gift_info } = await store.getConfig(activeIndex.value)
  giftConfigInfo.value = gift_info
}
getGiftInfo()

</script>

3.ts定义异步空返回值函数

const getList = async (): Promise<void> => {
    const {data} = await getListApi();
    ...
}