TypeScript基础

文章目录

数据类型

1.基础类型

  1. 安装nodejs环境
  2. 全局安装TypeScript
npm i typescript -g

3.写ts代码

let str: string = "sz"
console.log(str);

4.控制台终端输入命令

1. tsc -init
2. tsc -w
//就会生成一个js文件。

5.上面那个终端不要关,在开一个,用node来执行js文件

node index.js
//输出 sz

案例1:

支持es6语法

let num: number = 123
let str: string = `${num}`
console.log(str,typeof(str));//123 string

还有一些其他的

image-20230705080009006

布尔值

let b1:boolean = true
let b2:boolean = false

null和undefined

let n:null = null
let b:undefined = undefined
let v1:void = null//严格模式下就报错
let v2:void = undefined

需要在tsconfig.json里面找到strict设置为false

void一般用与函数的声明。

function myFn():void{
  return //返回一个空
}

2.任意类型

上面调试编译太麻烦了,可以全局安装第三方库

npm i ts-node -g

然后打开项目控制台终端

npm init -y
//生成一个package.json文件

然后需要装一个声明文件

npm i @types/node -D
let a: number = 123
console.log(a);

只需要直接在终端输入下面命令,就可以运行ts代码

ts-node index.ts

any类型:任意类型 ,unknow 不知道的类型.

数据类型可分为下面几大类

1.top type 顶级类型 any ,unknown
2. Object 
3.Number,String,Boolean
4.number,string,boolean
5. 1 ,  'sz' , false
6. never

any是可以赋值给别等数据类型的,unknown是不可以赋值给别等类型。


let a:any = 1
let b:number = 5
//any是可以
a=b
b=a
//unknown是不可以的
let c:unknown=4
b = c//这样是不行的。

unknown类型和any类型的却别

1.unknown只能赋值给自身或者是any。

2.unknown没有办法读取任何属性 方法也不可以调用

3.any类型比unknown更加安全

案例1:

let sz:unknown = {age:18,open:()=>123}
//这样是读不到的,未知
sz.age
sz.open()

上面any类型是可以的。

3. Object object 以及{}

1.Object

原型链顶端就是这个Object或者function。

意味着所有的原始类型,以及对象类型,最终都指向这个Object。

在ts中,Object就包含了所有类型,拿它就可以等于任何一个值

案例:

let a:Object = 123
let a1:Object = '123'
let a2:Object = []
let a3:Object = {}
let a4:Object = ()=>123

2.object

这个东西一般常用于泛型约束,代表非原始类型的一个类型。

所有原始类型就不支持。支持所有的引用类型

//不支持
let a:object = 123
let a1:object = '123'
let a0:object = false
//支持
let a2:object = []
let a3:object = {}
let a4:object = ()=>123

3. {} 空对象模式

这个和Object一样,也是支持所有数据类型的

let a:{}//new Object
//支持
let a:{} = 123
let a1:{} = '123'
let a2:{} = []
let a3:{} = {}
let a4:{} = ()=>123

补充:

{}(字面量模式),他虽然可以赋值任意数据类型,但是赋值完之后,对象类型,是没法增加和修改的

let a:{}={name:1}
a.age=18//报错

但是赋值基础数据类型貌似可以修改

let a:{}=123
a=18
console.log(a);//18

这个类型还是少用为好。

接口和对象类型

1.接口interface

interface Axxxs{ //注意首字母要大写
  name:string
}
let a:Axxxs = { //这样可以定义对象的一个类型
  name:'sz'
}

注意:不能多属性,也不能少属性。他俩必须要长的一摸一样

比如想加入一个age属性,必须两个都有

interface Axxxs{ //注意首字母要大写
  name:string
  age:number
}
let a:Axxxs = { //这样可以定义对象的一个类型
  name:'sz',
  age:18
}

1.1interface 重名 重合

案例

interface Axxxs{ //注意首字母要大写
  name:string
  age:number
}
interface Axxxs{
  ikun:string
}
let a:Axxxs = { //这样可以定义对象的一个类型
  name:'sz',
  age:18,
  ikun:'cxk'
}

上面遇到重名,他自己就重合,变成这样

interface Axxxs{ //注意首字母要大写
  name:string
  age:number
  ikun:string
}

1.2 任意key

比如这个对象,调接口,后端获得的,里面有杂七杂八各种属性。

但是我只想要name属性,而其他的属性有时候会用到,有时候也不会用到。

但是太多了,又懒的定义。这时候就可以用

索引签名

interface Axxxs{ //注意首字母要大写
  name:string
  age:number
  [propName:string]:any//这时候下面对象就可以随便定义类型
}

let a:Axxxs = { //这样可以定义对象的一个类型
  name:'sz',
  age:18,
  //下面几个ts就不会进行校验了
  a:1,
  b:'123'
}

1.3 ?和readonly

?

interface Axxxs{ //注意首字母要大写
  name:string
  age?:number//这个值可以写,也可以不写
 
}
let a:Axxxs = { //这样可以定义对象的一个类型
  name:'sz',
  age:18,
}

readonly

当不想被改变值的时候,就加入这个修饰符

interface Axxxs{
  name:string
  age:number
 readonly id:number
 readonly cb:()=>boolean
}
let a:Axxxs = {
  name:'sz',
  age:18,
  id:1
  cb:()=>{
    return false
  }
}

//可以进行调用
	a.cb()
//不可以更改
 a.cb=()=>{return true}

使用场景:

1.函数,不想随便更改

2.后台返回的id也不想更改

1.4 接口继承

用extend去继承,可以写多个

interface Axxxs extends B{ //相当于两个合并到一块了
  name:string
  age:number
 readonly id:number
 readonly cb:()=>boolean
}

interface B{
  xxx:string
}
let a:Axxxs = {
  name:'sz',
  age:18,
  id:1,
  xxx:"xxx",
  cb:()=>{
    return false
  }
}

1.5 定义函数类型

下面约束了函数的返回值和参数类型

interface Fn {
  (name:string):number[]
}
const fn:Fn =function (name:string){
  return [1]
}

2.数组类型

2.1数组定义

第一种方式:工作用的会多一点

//数字类型的数组
let arr:number[]=[1,2,3,4]
//boolean类型的数组
let arr:boolean[]=[true,false]

第二种方式:泛型的方式

//数组的普通类型
let arr:Array<boolean> = [true,false]

2.2数组对象

如何定一个对象数组,使用interface

interface X{
  name:string
  age?:number
}
let arr:X[] = [{name:'sz'},{name:'123'}]

2.3 二维数组

一般方式

let arr:number[][]=[[1],[2],[3]]

泛型的方式

let arr:Array<Array<number>>=[[1],[2],[3]]

如果数组里面各种类型都有,就直接定义成any类型的

let arr:any[] = [1,'asss',true,{}]

案例

function a(...args:any[]){ //或者定义成number类型
  console.log(args)//[1,2,3]
}
a(1,2,3)

arguments,类数组(伪数组),它没有数组的一些方法。比如forEach

function a(...args:string[]){
  console.log(arguments)//{ '0': '1', '1': '2' }
}
a('1','2')

如何定义?

ts提供一个内置对象 IArguments,来定义类数组

function a(...args:string[]){
 	let a:IArguments = arguments
  console.log(a)//{ '0': '1', '1': '2' }
}
}
a('1','2')

原理:

function a(...args:string[]){
 	let a:A = arguments.callee
}
}
a('1','2')
interface A {
  callee:Function
  length:number
  [index:number]:any
}

3.函数类型

3.1 函数定义类型和返回值

普通函数

function add(a: number, b: number): number{ //最后这个number是返回值的类型
    return a + b
}
console.log(add(1,2));

箭头函数,类似的

const add = (a:number,b:number):number=>{
  return a+b
}
console.log(add(1,2))

3.2 函数的参数

不想穿参数,给新参一个默认值

const add = (a:number=10,b:number = 10):number=>{
  return a+b
}
console.log(add())//20

3.3 函数的参数是一个对象

 interface User {
   name:string
   age:number
 }
function add(user:User):User{
  return user
}
console.log(add({name:'sz',age:18}))//{ name: 'sz', age: 18 }

3.4 函数this类型

ts可以定义this的类型 在js中无法使用。

必须是第一个参数定义this 的类型

interface Obj{
    user:number[]
   add:(this:Obj,num:number)=>void
 }
 
 let obj:Obj = {
   user:[1,2,3],
   add(this: Obj,num:number) {//传参数的时候忽略第一个this参数,num当成第一个
       this.user.push(num)
   }
 }
	obj.add(4)
 console.log(obj);
//{ user: [ 1, 2, 3, 4 ], add: [Function: add] }

3.5 函数重载

比如 很多方法只在一个函数里面去实现

同步不同的参数类型,可以在一个函数里面实现增删改查。

//findOne 查找一个参数
//find 查找索引
//add 帮我去做一个新增
//在一个函数里面去实现
let user:number[] = [1,2,3]
//函数重载写法
function findNum(add:number[]):number[]//如果传入的是一个number类型的数组那就做添加
function	findNum(id:number):number[]//如果传入id就是单个查询
function findNum():number[]//如果没有传入东西就是查询全部	
function findNum(ids?: number | number[]):number[]{  //区分不同的参数实现不同的功能
    if (typeof ids == 'number') {
        return user.filter(v=>v==ids)
    }
    else if (Array.isArray(ids)) {
        user.push(...ids)
        return user
    } else {    //如果什么都没传,就直接返回一个查询全部
        return user 
    }
}
//什么都没有传,返回user本身全部
console.log(findNum());//[1,2,3]
//传入一个数字
console.log(findNum(2));//[2]
//传入一个数组
console.log(findNum([1,4,5]));//[1, 2, 3, 1, 4, 5 ]

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

1.联合类型

//同时支持多个类型
let phone:number | string ='010-90923092s'

声明函数的时候也会使用

比如开发过程中开关状态会显示数字类的0和1,需要将它转为boolean类型

let fn = function (type:number|boolean):boolean{ //如果后台已经转为boolean值了,就参数再加个布尔类型
  return !!type //使用强转
}
//!!0 是false
//!!1是true

let result = fn(1)
console.log(result)

2.交叉类型

interface Poeple{
  name:string
  age:number
}

interface Man{
  sex:male
}

const sz=(man:People & Man):void=>{
  console.log(man)//{ name: 'sz', age: 18, sex: 'male' }
}
sz({
  name:'sz',
     age: 18,
    sex:'male'
})

3.类型断言

let fn = function (num: number | string): void{
    console.log((num as string).length); //6
}
fn('123123')

不能滥用这个类型断言,会导致类型错误

interface A{
    run:string
}
interface B{
    build:string
}
let fun = (type: A | B) => {
    
    console.log((type as A).run); //可以使用类型断言
    // console.log((<A>type).run); //或者这样
}
fun({
    build:'123'//像这样,run肯定打印不出来(undefined),所以类型断言不能滥用
})

案例:

(window as any).abc=123//any作为临时断言

案例:

const fn=(type:any):boolean=>{
  return type as boolean
}
console.log(fn(1))//数字1,并不会变成布尔值

内置对象 & 代码雨

1.ECMAScript内置对象

案例

let num:Number = new Number(1)
let data:Date = new Date()
let reg:RegExp = new RegExp(/\w/)
let error:Error = new Error('错了')
let xhr:XMLHttpRequest = new XMLHttpRequest()

dom

//HTML(元素名称)Element HTMLElement Elements
let div = document.querySelector('footer'//当元素是一个集合,可以定义NodeList
let div:NodeList =document.querySelectorAll('footer')
//div forEeach 去遍历当前这个div
//中间获取是动态的
//如果你获取的dom元素不固定的话,就传入HTMLElement就可以实现了
let div:NodeListOf<HTMLDivElement | HTMLElement>=document.querySelectorAll('div footer')

localStorage

let local:Storage = localStorage
let lo:Location = location
//返回的一个类型就要传入相应的类型
let promise:Promise<number> = new Promise((r)=>r(1))
promise.then(res=>{ //res是number	
})
let cookie:string = document.cookies

代码雨案例:

let canvas: HTMLCanvasElement = document.querySelector('canvas')!;
if (canvas) {
    let ctx = canvas.getContext('2d')
    canvas.width = screen.availWidth
    canvas.height = screen.availHeight
    let str: string[] = 'aaww1231sssss'.split('')

    const Arr: number[] = Array(Math.ceil(canvas.width / 10)).fill(0);
    console.log(Arr);

    const rain = () => {
        if (ctx) {
        ctx.fillStyle = 'rgba(0,0,0,0.05)'
        ctx.fillRect(0, 0, canvas.width, canvas.height)
            ctx.fillStyle = '#0f0'
            Arr.forEach((item,index) => {
                ctx?.fillText(str[Math.floor(Math.random() * str.length)], index * 10, item + 10)
                 Arr[index] = item > canvas.height||item > 10000 * Math.random() ? 0 :item + 10
            })
        }   
    }
    setInterval(rain,40)
}   

效果:

image-20230719000459775

Class类

1.Class的基本用法,继承和类型约束

案例:


interface Options{
    el:string | HTMLElement
}
interface VueCls{
    options: Options
    init():void
}
//继承
interface Vnode {
    tag : string //标签 div section header
    text?:string //123
    children?:Vnode[]
  }
 //虚拟dom简单版
class Dom {
     //创建节点的方法
     createElement(el:string) {
       return  document.createElement(el)
     }
    //填充文本方法
    setText(el: HTMLElement, text: string | null) {
        el.textContent = text
    }
    //渲染函数,把js描述的dom变成一个真实的dom
    render(data:Vnode) {
        let root = this.createElement(data.tag)
        //如果子节点有值,并且是个数组
        if (data.children && Array.isArray(data.children)) {
            data.children.forEach(item => {
                //不确定里面有多少层,进行递归操作,一直去创建children节点
                let child = this.render(item)
                root.appendChild(child)
            })
        } else {
            //将文本塞到节点里面
            if (typeof data.text !== 'undefined') {
                this.setText(root, data.text)
            }
        }
        return root
    }
 }
class Vue extends Dom implements VueCls { 
    
    options: Options //定义一个options
    constructor(options: Options) {
        super()
        this.options = options
        this.init()
    }
    init(): void {
        //虚拟dom,就是通过js 去渲染真实的dom
        let data:Vnode={
            tag: 'div',
            children: [
                {
                    tag: "section",
                    text:"我是子节点1"
                },
                {
                    tag: "section",
                    text:"我是子节点2"
                }
            ]
        }  
        //判断类型,如果是string类型,就进行一个获取,如果不是string,就一个dom节点,直接返回就可以
        let app = typeof this.options.el == 'string' ? document.querySelector(this.options.el) : 							this.options.el
        //把生成的真实的dom节点给它塞进去
        app?.appendChild(this.render(data))
        
        //子类里面去调用父类里面的render方法
        this.render(data)
    }
}
new Vue({
    el:"#app"
})

index.html

<div id="app"></div>
    <script src="./index.js"></script>

效果图:

image-20230720074534466

2.class 的修饰符

readonly:只读

private:只能在内部使用

比如

class Dom {   
//创建节点的方法
   private createElement(el:string) {
       return  document.createElement(el)
     }
    //填充文本方法
   private setText(el: HTMLElement, text: string | null) {
        el.textContent = text
    }
}

只能在父类的内部去使用这个方法.

他是一个私有的,外面也没有办法调用

//比如实例化之后
let dom = new DOM()
dom. //没办法调用内部私有的方法

protected:给子类和内部使用的

public:所有的方法默认是public,可以给子类用,可以给自身用,也可以给外部使用

3.super原理

//为什么继承之后这里要加个super
 constructor(options: Options) {
        super() //父类的prototype.constructor.call
        this.options = options
        this.init()
    }
//super指向的是父类,可以通过super调用父类的方法
	super.render()

4.静态方法

属性和方法都是可以使用的

static version(){
  return '	1.0.0'
}
new Vue({
    el:"#app"
})
//通过大的实例去调用的
Vue.version()

弹幕:static属性加载在其他属性之前,static初始化的时候别的属性还不存在,所以调用不了。

5.get 和 set

class Ref {
    _value : any
    constructor(value:any){
      this._value = value;
    }
    //可以自定义去拦截一个读取操作和设置操作
    get value(){
      return this._value + 'vvvv'
    }
    set value(newVal){
      this._value = newVal + 'sz'
    }
  }
  
  const ref = new Ref("哈哈哈")

  console.log(ref.value)//哈哈哈vvvv
  //设置操作
  ref.value="坏人"
console.log(ref.value)//坏人szvvvv

所以可以通过git和set作为一个拦截器

抽象类

//abstract 所定义的抽象类
//abstract 所定义的方法 都只能描述,不能进行一个实现
//抽象类无法被实例化
abstract class Vue{
    name:string
    constructor(name?:string) {
        this.name=name!//加!表示明确不为空
    }
    getName():string {
        return this.name
    }
    //加了abstract就只能进行描述
    abstract init(name:string):void
}
  //抽象类是不能被实例化的
  //new Vue()//会报错
	
	//定义一个派生类去继承这个抽象类
	class React extends Vue{
     constructor() {
          super()
      }
    //将抽象类里面的方法进行一个实现
      init(name:string) {
      }
    //因为name已经在抽象类里面定义过了,所以在派生类不需要重新定义
    setName(name: string) {
        this.name=name
    }
  }
//派生类是可以被实例化的
const react = new React()
react.setName('sz')
  console.log(react.getName());//sz

元祖类型

//元祖类型的
let arr:[number,boolean]=[1,true]
arr.push(null)//因为推断它是个number和string的联合类型,除了这两种类型其他类型都是不可以的

如果不想让他修改数组里面的值,并且不想让他使用push,就可以加一个readonly

let arr:readonly [x:number,y?:boolean]=[1]

实际应用场景

let excel:[string,string,number][]= [
  ['sz','男',18],
  ['sz','男',18],
  ['sz','男',18],
  ['sz','男',18],
]

元祖的类型

let arr:readonly [x:number,y?:boolean]=[1]
type first =  type of arr[0]

let arr:readonly [x:number,y:boolean]=[1,false]
type first = typeof arr['length']//2

枚举类型

1.数字枚举

enum Color{
    red,
    green,
    blue
}
console.log(Color.red);//0
console.log(Color.green);//1
console.log(Color.blue);//2

2.增长枚举

enum Color{
    red = 1,
    green,
    blue
}
console.log(Color.red);//1
console.log(Color.green);//2
console.log(Color.blue);//3

3.字符串枚举

enum Color{
    red='red',
    green='green',
    blue='blue'
}
console.log(Color.red);//red
console.log(Color.green);//green
console.log(Color.blue);//blue

弹幕:枚举,是为了可以更清晰明了的看出来具体的某个值的实际意义。

枚举比object的好处也就字面量到值 ,和值 到字面量转换方便点。

3.异构枚举

枚举可以混合字符串和数字成员

  enum Color {
    yes = 1,
    no = 'no'
  }
  console.log(Color.yes)//1
  console.log(Color.no)//no

4.接口枚举

定义一个枚举 定义一个接口A 他有一个属性red 值为Color.yes

声明对象的时候要遵循这个规则

enum Color {
  yes = 1,
  no = 'no'
}
interface A {
  red:Color.yes
}
let obj: A = {
  red:Color.yes
}

5.const枚举

let 和 var 都是不允许的声明只能使用const

大多数情况下,枚举是十分有效的方案。 然而在某些情况下需求很严格。 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举。 常量枚举通过在枚举上使用 const修饰符来定义

const 声明的枚举会被编译成常量

//如果用const定义
const enum Types{
  sucess,
  fail
}

let code: number = 0
if (code === Types.sucess) {
  
}

执行tsc index.ts查看js文件,此昂成一个常量

var code = 0;
if (code === 0 /* Types.sucess */) {
}

如果不加const,就变成一个对象

var Types;
(function (Types) {
    Types[Types["sucess"] = 0] = "sucess";
    Types[Types["fail"] = 1] = "fail";
})(Types || (Types = {}));
var code = 0;
if (code === Types.sucess) {
}

6.反向映射

它包含了正向映射( name -> value)和反向映射( value -> name

要注意的是 不会为字符串枚举成员生成反向映射。

 enum Types{
  sucess,
  fail
}
let sucess: number = Types.sucess
let key = Types[sucess]
console.log(`value----${sucess}`,`key----${key}`);//value----0 key----sucess

字符串不支持

 enum Types{
  sucess='456',
}
let sucess: string = Types.sucess
let key = Types[sucess]
console.log(`value----${sucess}`,`key----${key}`);//报错

类型推论|类型别名

类型推论

ts天然支持的类型推论

let str = 'sz'//鼠标放在str上时,他会推断是个string类型
 str = 456 //这样是无效的
//数组,对象都是可以类型推论的
let arr = [1,2,4]//推断出是一个number[]

如果是这样,没有给变量进行赋值,也没有给变量定义类型,默认推断是any类型

let str 
str=123
str=null //不报错

类型别名

let s  =string | number //支持联合类型
let str:s = 'sz'

还可以定义参数

let s = (name:string)=>void

type跟interface的区别

1.interface可以用extends继承,type没法使用

interface A extends B{
}
interface B{
  
}
//type只能使用交叉类型
type s = number[] & B

2.interface是没有办法写联合类型的,type可以

type str = string | number[]

interface A extends B{
  //只能在内部属性定义联合类型
  name:string|number
}

3.interface遇到重名的,会进行合并,type是不会的

interface A {
  name:string|number
}
interface A{
  age:number
}

type的高级用法

左边的值会作为右边值的子类型遵循图中上下的包含关系

//左边的1会作为number的子类型,number包含这个1
type a = 1 extends number ? 1 : 0 //1
 
type a = 1 extends Number ? 1 : 0 //1
 
type a = 1 extends Object ? 1 : 0 //1
 
type a = 1 extends any ? 1 : 0 //1
 
type a = 1 extends unknow ? 1 : 0 //1
 //never是最底层的,包含不了基础类型,所以返回0
type a = 1 extends never ? 1 : 0 //0
image-20230727080440128

never类型

TypeScript 将使用 never 类型来表示不应该存在的状态(很抽象是不是)

// 返回never的函数必须存在无法达到的终点
// 因为必定抛出异常,所以 error 将不会有返回值
function error(message: string): never {
    throw new Error(message);
}
 
// 因为存在死循环,所以 loop 将不会有返回值
function loop(): never {
    while (true) {
    }
}

never和void的差别

差别一:

    //void类型只是没有返回值 但本身不会出错
    function Void():void {
        console.log();
    }
 
    //只会抛出异常没有返回值
    function Never():never {
    throw new Error('aaa')
    }

差异二:

当我们鼠标移上去的时候会发现 只有void和number never在联合类型中会被直接移除

type A = void | number | never

never类的一个应用场景

type A = '小满' | '大满' | '超大满' 
 
function isXiaoMan(value:A) {
   switch (value) {
       case "小满":
           break 
       case "大满":
          break 
       case "超大满":
          break 
       default:
          //是用于场景兜底逻辑
          const error:never = value;
          return error
   }
}

比如新来了一个同事他新增了一个篮球,我们必须手动找到所有 switch 代码并处理,否则将有可能引入 BUG 。

而且这将是一个“隐蔽型”的BUG,如果回归面不够广,很难发现此类BUG。

那 TS 有没有办法帮助我们在类型检查阶段发现这个问题呢?

type A = '小满' | '大满' | '超大满' | "小小满"
 
function isXiaoMan(value:A) {
   switch (value) {
       case "小满":
           break 
       case "大满":
          break 
       case "超大满":
          break 
       default:
          //是用于场景兜底逻辑
          const error:never = value;
          return error
   }
}

symbol类型

自ECMAScript 2015起,symbol成为了一种新的原生类型,就像numberstring一样。

symbol类型的值是通过Symbol构造函数创建的。

可以传递参做为唯一标识 只支持 string 和 number类型的参数

let sym1 = Symbol();
let sym2 = Symbol("key"); // 可选的字符串key

1.symbol的值是唯一的

const a1:symbol = Symbol(1)//唯一的
const a2:symbol = Symbol(1)
//他们两个内存地址是不一样的,各自创建各自的内存地址	
console.log(a1===a2)//false

扩展:如何让两个symbol返回一个true

console.log(Symbol.for('ss')===Symbol.for('ss'));//true
//for他会在全局的symbol里面找有没有注册过这个key,如果有的话直接拿来用,不会创建新的
//没有的话,他就去创建一个

具体场景

const a1:symbol = Symbol(1)//唯一的
const a2: symbol = Symbol(1)
let obj = {
  name: 1,
  [a1]: 111,//索引签名的方式
  [a2]:222//他们两key一样,但是不会进行覆盖
}

console.log(obj);//{ name: 1, [Symbol(1)]: 111, [Symbol(1)]: 222 }

这就是symbol出现的原因:就是为了解决这个属性的key重复的问题,可以做到一个防重的效果

2.如何获取symbol 的key

如何取这个key

//for in 不能读到symbol
for(let key in obj){
  console.log(key)//name
}
//Object.keys
//发现也是读不到symbol
console.log(Object.keys(obj))//['name']
//Object.getOwnPropertyNames也是去不到keys

//可以通过Object.getOwnPropertySymbols()
console.log(Object.getOwnPropertySymbols(obj))//[ Symbol(1), Symbol(1)]

//name和symbol同时取出来
//通过es6的Reflect.ownKeys()
console.log(Reflect.ownKeys(obj))//[ 'name', Symbol(1), Symbol(1) ]

迭代器|生成器

1.生成器

function* gen(){
  yield Promise.resolve('sz')//同步异步
  yield 'sz111'
}
const man = gen()
//按照调用的顺序来的
console.log(man.next())//{ value: Promise { 'sz' }, done: false }
console.log(man.next())//{ value: 'sz111', done: false }
console.log(man.next())//{ value: undefined, done: true }
//done为true代表已经没有东西可以迭代了

2.迭代器

1.set map

//1.set结构
let set:Set<number> = new Set([1,1,2,3,3,4])//天然去重,元素支持数字和字符串	
console.log(set)//Set(4) { 1, 2, 3, 4 }

//2.map结构
let map:Map<any,any> = new Map()
let Arr = [1,2,4]
map.set(Arr,'sz')//可以把数组里面元素当场key
//取值
console.log(map.get(Arr))//sz

function args(){
  console.log(arguments)//获取参数的一个集合,它是一个伪数组(类数组)
}
//这也是伪数组,不能使用数组的一些内置方法
let list = document.querySelectorAll('div')

2.Symbol.iterator 迭代器

var arr = [1,2,3,4];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next());  //{ value: 1, done: false }
console.log(iterator.next());  //{ value: 2, done: false }
console.log(iterator.next());  //{ value: 3, done: false }
console.log(iterator.next());  //{ value: 4, done: false }
console.log(iterator.next());  //{ value: undefined, done: true }

测试用例

这个方法可以支持各种类型的数据

let set:Set<number> = new Set([1,1,2,3,3,4])
let map:Map<any,any> = new Map()
let Arr = [1,2,4]
map.set(Arr,'sz')//可以把数组里面元素当场key
const each = (value: any) => {
  let It: any = value[Symbol.iterator]()
  let next: any = { done: false }
  //如果为true就停掉循环
  while (!next.done) {
    next = It.next()
    if (!next.done) {
      console.log(next.value);
    }
  }
}
each(map)//[ [ 1, 2, 4 ], 'sz' ]  分别是key和value
each(set)//1 2 3 4
each(Arr)//1 2 4

3.迭代器语法糖

上面方法语法糖:for of

let set:Set<number> = new Set([1,1,2,3,3,4])
for(let value of set){
  console.log(value)
}

for of 对象不能用

for(let value of {name:1}){}//报错,因为对象身上是没有[Symbol.iterator]()方法

4.解构

let [a,b,c]=[4,5,6]
console.log(a,b,c)//4 5 6

底层原理,也是去调用iterator

5.如何让obj支持for of方法

手动添加[Symbol.iterator]()方法。

let obj={
  max:5,
  current:0,
  [Symbol.iterator](){
    return {	
      max:this.max,
      current:this.current,
      next(){
          if(this.current == this.max){ //迭代结束
            return{
              value:undefined,
              done:true
            }
          }else{
          return{
          value:this.current++,
          done:false
        }
        }
      }
    }
  }
}
for (let value of obj) {
  console.log(value);//0 1 2 3 4 
}
//数组解构
let x = [...obj]
console.log(x)//[0,1,2,3,4]
//对象解构
let a = {...obj}
console.log(a)
//{
 // max: 5,
  //current: 0,
 // [Symbol(Symbol.iterator)]: [Function: [Symbol.iterator]]
//}

注意:数组解构和对象解构是不一样的

数组解构底层调用的是[Symbol.iterator]()

而对象底层不是调用这个。

泛型

1.泛型定义

也可以称之为动态类型

function sz(a:number,b:number):Array<number>{
  return [a,b]
}
//如果传入string类型,还需要再写一个
function sz(a:srting,b:string):Array<string>{
  return [a,b]
}

所以出现动态类型(泛型)来解决这一问题

function sz<T>(a:T,b:T):Array<T>{
  return [a,b]
}
sz(1,2)
//全称:sz<number>(1,2),一般不这样写,让它自己推断就行了
sz('1','2')
sz(false,true)

总结:在定义一个函数的时候,传入的类型不明确,就可以使用泛型,在调用的时候,再去明确它的类型

除了函数之外,interface和type都可以用泛型。

类型别名

type A<T>=string | number | T
let a:A<boolean> = ture	 //1  'sz'
let a:A<undefined> = undefined
let a:A<null> = null

接口interface

interface Data<T>{
  msg:T
}
let data:Data<string>={
  msg:'sz无了'
}

其他泛型用法

//泛型支持多个
function add<T,K>(a:T,b:K):Array<T | K>{
  return [a,b]
}
console.log(add(1,false));//[ 1, false ]
add()//什么都不传,推断的就是个number

2.axios封装

data.json

{
    "message":"成功",
    "code":200
}

index.ts

const axios = {
  get<T>(url:string):Promise<T>{
    return new Promise((resolve,reject)=>{
      let xhr: XMLHttpRequest = new XMLHttpRequest()
      //发起请求的一个方式
      xhr.open('GET', url)
      //监听状态变化的方法
      xhr.onreadystatechange = () => {
        if (xhr.readyState == 4 && xhr.status==200) {
          resolve(JSON.parse(xhr.responseText))
        }
      }  
      xhr.send(null)
    })
  }
}
interface Data {
  message: string,
  code:number
}
axios.get<Data>('./data.json').then(res => {
  console.log(res);//
})

1.控制台输入命令tsc

2.vscode安装live serve插件

3.在index.html文件右键选择Open with Live Serve

打印结果:

image-20230730202423940

泛型约束

1.泛型约束案例

在类型后面跟一个extends,在跟一个约束的类型

function add<T extends number>(a:T,b:T){
  return a + b
}
console.log(add(1,2));//3

​ 案例2:

interface Len {
    length:number
}

function fn<T extends Len>(a: T) {
  console.log(a.length);
  
}
fn('13234')//5
fn([1,2,3,4])//4

2. 约束对象的key

let obj = {
  name: 'sz',
  sex:'男'
}
///age keyof
type Key = keyof typeof obj//将对象身上的key推断成一个联合类型
//第一个参数T希望传入一个引用类型,第二个参数K希望传入对象身上的一个key
function ob<T extends object, K extends keyof T>(obj: T, key: K) {
    return obj[key]
}
console.log(ob(obj,'name'));//sz

如果要约束对象的key,一定要使用keyof

keyof高级用法:

interface Data{
  name: string,
  age: number,
  sex:string
}
//使用泛型工具,将他们都变成可选状态
type Options<T extends object> = {
  //类似于for in
  [Key in keyof T]?:T[Key]
}

type B =Options<Data>

发现都加上了问号

image-20230730205843189

同理不想加?,可以给它加个readonly

readonly [Key in keyof T]:T[Key]

tsconfig.json配置文件

1.生成sconfig.json

tsc --init

2.配置详情

"compilerOptions": {
  "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
  "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
  "diagnostics": true, // 打印诊断信息 
  "target": "ES5", // 目标语言的版本
  "module": "CommonJS", // 生成代码的模板标准
  "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
  "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
  "allowJS": true, // 允许编译器编译JS,JSX文件
  "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
  "outDir": "./dist", // 指定输出目录
  "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
  "declaration": true, // 生成声明文件,开启后会自动生成声明文件
  "declarationDir": "./file", // 指定生成声明文件存放目录
  "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
  "sourceMap": true, // 生成目标文件的sourceMap文件
  "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
  "declarationMap": true, // 为声明文件生成sourceMap
  "typeRoots": [], // 声明文件目录,默认时node_modules/@types
  "types": [], // 加载的声明文件包
  "removeComments":true, // 删除注释 
  "noEmit": true, // 不输出文件,即编译后不会生成任何js文件
  "noEmitOnError": true, // 发送错误时不输出任何文件
  "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
  "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
  "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
  "strict": true, // 开启所有严格的类型检查
  "alwaysStrict": true, // 在代码中注入'use strict'
  "noImplicitAny": true, // 不允许隐式的any类型
  "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
  "strictFunctionTypes": true, // 不允许函数参数双向协变
  "strictPropertyInitialization": true, // 类的实例属性必须初始化
  "strictBindCallApply": true, // 严格的bind/call/apply检查
  "noImplicitThis": true, // 不允许this有隐式的any类型
  "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
  "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
  "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
  "noImplicitReturns": true, //每个分支都会有返回值
  "esModuleInterop": true, // 允许export=导出,由import from 导入
  "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
  "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
  "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
  "paths": { // 路径映射,相对于baseUrl
    // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
    "jquery": ["node_modules/jquery/dist/jquery.min.js"]
  },
  "rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
  "listEmittedFiles": true, // 打印输出文件
  "listFiles": true// 打印编译的文件(包括引用的声明文件)
}
 
// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
"include": [
   "src/**/*"
],
// 指定一个排除列表(include的反向操作)
 "exclude": [
   "demo.ts"
],
// 指定哪些文件使用该配置(属于手动一个个指定文件)
 "files": [
   "demo.ts"
]

介绍几个常用的

1.include
指定编译文件默认是编译当前目录下所有的ts文件

2.exclude
指定排除的文件

3.target
指定编译js 的版本例如es5 es6

4.allowJS
是否允许编译js文件

5.removeComments
是否在编译过程中删除文件中的注释

6.rootDir
编译文件的目录

7.outDir
输出的目录

8.sourceMap
代码源文件

9.strict
严格模式

10.module
默认common.js 可选es6模式 amd umd 等

namespace命名空间

我们在工作中无法避免全局变量造成的污染,TypeScript提供了namespace 避免这个问题出现

  • 内部模块,主要用于组织代码,避免命名冲突。

  • 命名空间内的类默认私有

  • 通过 export 暴露

  • 通过 namespace 关键字定义

    TypeScript与ECMAScript 2015一样,任何包含顶级import或者export的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的(因此对模块也是可见的)

ok,让我们看一个小例子

1.命名空间案例

命名空间中通过export将想要暴露的部分导出

如果不用export 导出是无法读取其值的

namespace a {
    export const Time: number = 1000
    export const fn = <T>(arg: T): T => {
        return arg
    }
    fn(Time)
}
 
 
namespace b {
     export const Time: number = 1000
     export const fn = <T>(arg: T): T => {
        return arg
    }
    fn(Time)
}
a.Time
b.Time

2.嵌套命名空间

namespace a {
    export namespace b {
        export class Vue {
            parameters: string
            constructor(parameters: string) {
                this.parameters = parameters
            }
        }
    }
}
let v = a.b.Vue
new v('1')

3.抽离命名空间

a.ts

export namespace V {
    export const a = 1
}

b.ts

import {V} from '../observer/index'
console.log(V); //{a:1}

简化命名空间

namespace A  {
    export namespace B {
        export const C = 1
    }
}
import X = A.B.C
console.log(X);//1

4.合并命名空间

重名的命名空间会合并

image-20230731224619986

三斜线指令

弹幕:

三斜线注释主要用于旧版本的TypeScript或与特定工具和库的集成中。

在新的TypeScript项目中,不再需要使用三斜线注释进行模块引用,而是通过import语句来导入其他模块。

ts3.0以后就不建议用这个了,建议用import.


三斜线指令是包含单个XML标签的单行注释。 注释的内容会做为编译器指令使用。

三斜线指令仅可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。 如果它们出现在一个语句或声明之后,那么它们会被当做普通的单行注释,并且不具有特殊的涵义。

/// 指令是三斜线指令中最常见的一种。 它用于声明文件间的 依赖。

三斜线引用告诉编译器在编译过程中要引入的额外的文件。

你也可以把它理解能import,它可以告诉编译器在编译过程中要引入的额外的文件

例如a.ts

namespace A {
    export const fn = () => 'a'
}

b.ts

namespace A {
    export const fn2 = () => 'b'
}

index.ts

引入之后直接可以使用变量A

///<reference path="./index2.ts" />
///<reference path="./index3.ts" />
console.log(A);

声明文件引入

例如,把 /// 引入到声明文件,表明这个文件使用了 @types/node/index.d.ts里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。

仅当在你需要写一个d.ts文件时才使用这个指令。

///<reference types="node" />

注意事项:

如果你在配置文件 配置了noResolve 或者自身调用自身文件会报错

image-20230731231715943

声明文件

1.声明文件declare

ts当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。

declare var 声明全局变量
declare function 声明全局方法
declare class 声明全局类
declare enum 声明全局枚举类型
declare namespace 声明(含有子属性的)全局对象
interface  type 声明全局类型
/// <reference /> 三斜线指令

例如我们有一个express 和 axios
image-20230801075517623

发现express 报错了

让我们去下载他的声明文件

npm install @types/node -D

那为什么axios 没有报错

我们可以去node_modules 下面去找axios 的package json

image-20230801075548117

发现axios已经指定了声明文件 所以没有报错可以直接用

通过语法declare 暴露我们声明的axios 对象

declare const axios: AxiosStatic;

如果有一些第三方包确实没有声明文件我们可以自己去定义

名称.d.ts 创建一个文件去声明

案例手写声明文件
index.ts

2.手写声明文件

index.ts

import express from 'express'
 
 
const app = express()
 
const router = express.Router()
 
app.use('/api', router)
 
router.get('/list', (req, res) => {
    res.json({
        code: 200
    })
})
app.listen(9001,()=>{
    console.log(9001)
})

express.d.ts

declare module 'express' {
    interface Router {
        get(path: string, cb: (req: any, res: any) => void): void
    }
    interface App {
 
        use(path: string, router: any): void
        listen(port: number, cb?: () => void): void
    }
    interface Express {
        (): App
        Router(): Router
 
    }
    const express: Express
    export default express
}

Mixins混入

TypeScript 混入 Mixins 其实vue也有mixins这个东西 你可以把他看作为合并

1.对象混入

可以使用es6的Object.assign 合并多个对象

此时 people 会被推断成一个交差类型 Name & Age & sex;

interface Name{
    name:string
}
interface Age{
    age:number
}
interface Sex{
    sex:boolean
}
let people1: Name = {
    name:'sz'
}
let people2: Age = {
    age:15
}
let people3: Sex = {
    sex:true
}
const people = Object.assign(people1, people2, people3)
console.log(people);//{ name: 'sz', age: 15, sex: true }

2.类的混入

首先声明两个mixins类 (严格模式要关闭不然编译不过)

class A {
    type: boolean = false;
    changeType() {
        this.type = !this.type
    }
}
 
class B {
    name: string = '张三';
    getName(): string {
        return this.name;
    }
}

下面创建一个类,结合了这两个mixins

首先应该注意到的是,没使用extends而是使用implements。 把类当成了接口

我们可以这么做来达到目的,为将要mixin进来的属性方法创建出占位属性。 这告诉编译器这些成员在运行时是可用的。 这样就能使用mixin带来的便利,虽说需要提前定义一些占位属性

class C implements A,B{
    type:boolean
    changeType:()=>void;
    name: string;
    getName:()=> string
}

最后,创建这个帮助函数,帮我们做混入操作。 它会遍历mixins上的所有属性,并复制到目标上去,把之前的占位属性替换成真正的实现代码

Object.getOwnPropertyNames()可以获取对象自身的属性,除去他继承来的属性,
对它所有的属性遍历,它是一个数组,遍历一下它所有的属性名

Mixins(C, [A, B])
function Mixins(curCls: any, itemCls: any[]) {
    itemCls.forEach(item => {
      console.log(item);//[class A]  [class B]
        Object.getOwnPropertyNames(item.prototype).forEach(name => {
            curCls.prototype[name] = item.prototype[name]
        })
    })
}
let ccc= new C() 
console.log(ccc.type)//false
ccc.changeType()
console.log(ccc.type)//true

装饰器Decorator

面试经常问,装饰器可以干什么,优势是什么。

Decorator 装饰器是一项实验性特性,在未来的版本中可能会发生改变

它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能

若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用编译器选项

image-20230801232305801

1.类装饰器

const Base:ClassDecorator = (target) =>{ //首字母大写 target是Http的构造函数
	console.log(target)//	[class Http]
   target.prototype.name = "sz",
        target.prototype.fn = () => {
        console.log('我是sz');
        }
}
@Base
class Http{
}
const http = new Http() as any

http.fn()
console.log(http.name);

不去破坏它自身的结构,从而给这个类去增加方法和属性

2.装饰器工厂

其实也就是一个高阶函数 外层的函数接受值 里层的函数最终接受类的构造函数

const Base = (name:string) => { //首字母大写 target是Http的构造函数
    const fn: ClassDecorator = (target) => {
        console.log(target)
    target.prototype.name = name,
        target.prototype.fn = () => {
        console.log('我是sz');
        }
    }
    return fn
}
@Base('sz123')
class Http{
}
const http = new Http() as any

console.log(http.name);//sz123

3.方法装饰器

实现一个get请求

import axios from "axios";
import 'reflect-metadata';
const Get = (url: string) => {
    const fn:MethodDecorator= (target,_:any,descriptor:PropertyDescriptor)=>{
        // console.log(target, key, descriptor);
            //     {} getList {
    //         value: [Function: getList],
    //         writable: true,
    //         enumerable: false,
    //         configurable: true
    //       }
      const key = Reflect.getMetadata('key',target)
        axios.get(url).then(res => {
          descriptor.value(key ?res.data:res[key].data)
        })

     }
    return fn
}
const Result =()=>{
  //参数装饰器
    const fn: ParameterDecorator = (target,key,index) => { //index是参数所在的位置
        Reflect.defineMetadata('key','result',target)
    }
    return fn
}
  class Http{
    @Get('https://api.apiopen.top/api/getHaoKanVideo?page=0&size=10')
      getList(@Result() data:any){
      console.log(data)//
    }
    create(){
      
    }
  }

4.属性装饰器

const Name: PropertyDecorator = (target,key) => {
  console.log(target,key);//{} name  
}
class Http{
  @Name
  name: string
  constructor() {
    this.name='sz'
  }
  }

构建ts项目

1.Rollup构建TS项目

Rollup构建TS项目
安装依赖
1.全局安装rollup npm install rollup-g

2.安装TypeScript npm install typescript -D

3.安装TypeScript 转换器 npm install rollup-plugin-typescript2 -D

4安装代码压缩插件 npm install rollup-plugin-terser -D

5 安装rollupweb服务 npm install rollup-plugin-serve -D

6 安装热更新 npm install rollup-plugin-livereload -D

7引入外部依赖 npm install rollup-plugin-node-resolve -D

8安装配置环境变量用来区分本地和生产 npm install cross-env -D

9替换环境变量给浏览器使用 npm install rollup-plugin-replace -D

webpack rollup打包对比

image-20230806214850386 image-20230806214918972

配置json文件

npm init -y

{
  "name": "rollupTs",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "cross-env NODE_ENV=development  rollup -c -w",
    "build":"cross-env NODE_ENV=produaction  rollup -c"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "cross-env": "^7.0.3",
    "rollup-plugin-livereload": "^2.0.5",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-replace": "^2.2.0",
    "rollup-plugin-serve": "^1.1.0",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.31.1",
    "typescript": "^4.5.5"
  }
}

配置rollup文件

console.log(process.env);
import ts from 'rollup-plugin-typescript2'
import path from 'path'
import serve from 'rollup-plugin-serve'
import livereload from 'rollup-plugin-livereload'
import { terser } from 'rollup-plugin-terser'
import resolve from 'rollup-plugin-node-resolve'
import repacle from 'rollup-plugin-replace'
 
const isDev = () => {
    return process.env.NODE_ENV === 'development'
}
export default {
    input: "./src/main.ts",
    output: {
        file: path.resolve(__dirname, './lib/index.js'),
        format: "umd",
        sourcemap: true
    },
 
    plugins: [
        ts(),
        terser({
            compress: {
                drop_console: !isDev()
            }
        }),
        repacle({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
        }),
        resolve(['.js', '.ts']),
        isDev() && livereload(),
        isDev() && serve({
            open: true,
            openPage: "/public/index.html"
        })
    ]
}

配置tsconfig.json

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */
 
    /* Projects */
    // "incremental": true,                              /* Enable incremental compilation */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./",                          /* Specify the folder for .tsbuildinfo incremental compilation files. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
 
    /* Language and Environment */
    "target": "es5",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
    // "experimentalDecorators": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */
    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
    // "reactNamespace": "",                             /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
 
    /* Modules */
    "module": "ES2015",                                /* Specify what module code is generated. */
    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
    // "moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */
    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "typeRoots": [],                                  /* Specify multiple folders that act like `./node_modules/@types`. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "resolveJsonModule": true,                        /* Enable importing .json files */
    // "noResolve": true,                                /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
 
    /* JavaScript Support */
    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
 
    /* Emit */
    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
      "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
    // "removeComments": true,                           /* Disable emitting comments. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types */
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like `__extends` in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing `const enum` declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
    // "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
 
    /* Interop Constraints */
    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
    /* Type Checking */
    "strict": true,                                      /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied `any` type.. */
    // "strictNullChecks": true,                         /* When type checking, take into account `null` and `undefined`. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
    // "noImplicitThis": true,                           /* Enable error reporting when `this` is given the type `any`. */
    // "useUnknownInCatchVariables": true,               /* Type catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
    // "noUnusedLocals": true,                           /* Enable error reporting when a local variables aren't read. */
    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read */
    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
    // "noUncheckedIndexedAccess": true,                 /* Include 'undefined' in index signature results */
    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type */
    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  }
}

npm run dev 启动

2.webpack构建TS项目

1.创建一个项目

npm init -y //生成package.json文件

安装依赖

安装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

然后输入

tsc --init //生成tsconfig.json文件

新建文件夹->文件

src -> index.ts

public -> index.html

新建一个webpack.config.js

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"
        })
    ]
}

3.esbuild + swc构建ts项目

  1. esbuild是go语言编写的并且是多线程执行,性能是js的好几十倍,所以很快。
  • 无需缓存即可实现基础打包
  • 支持 ES6 跟 CommonJS 模块
  • 支持ES 6 Tree Shaking
  • 体积小
  • 插件化
  • 其他
  • 内置支持编译 jsx

2.swc是用rust写的,所实现的功能跟babel一样,es6语法转es5,但是速度比babel更快,前端基建工具使用rust的是越来越多了,未来可能还会有一个替代postCss

npm install @swc/core esbuild @swc/helpers

其中,@swc/core 是 swc 的核心包,用于编译 JavaScript 和 TypeScript 代码;esbuild 是一个快速的 JavaScript 和 TypeScript 构建工具;@swc/helpers 是 swc 的辅助包,用于转换 JSX 代码。

config.ts文件

import esbuild from 'esbuild'//打包工具
import swc from '@swc/core'//类似于babel es6 转 es5
import fs from 'node:fs'
await esbuild.build({
    entryPoints: ['./index.ts'], //入口文件
    bundle: true, //模块单独打包
    loader: {
        '.js': 'js',
        '.ts': 'ts',
        '.jsx': 'jsx',
        '.tsx': 'tsx',
    },
    treeShaking:true,
    define: {
       'process.env.NODE_ENV': '"production"',
    },
    plugins: [
        {
            //实现自定义loader
            name: "swc-loader",
            setup(build) {
                build.onLoad({ filter: /\.(js|ts|tsx|jsx)$/ }, (args) => {
                   // console.log(args);
                    const content = fs.readFileSync(args.path, "utf-8")
                    const { code } = swc.transformSync(content, {
                        filename: args.path
                    })
                    return {
                        contents: code
                    }
                })
            },
        }
    ],
    outdir: "dist"
})

测试demo

export const a:number = 1
export const b:string = 'ikun'
let x = 1
let fn = () => 123
console.log(x,fn);

转移之后的


export var a = 1;
export var b = "ikun";
var x = 1;
var fn = function() {
  return 123;
};
console.log(x, fn);

实战插件编写

typescript封装LocalStorage并设置过期时间

目录结构

image-20230815075844611

package.json是新建的

然后输入命令,生成里面的内容

npm init -y

然后tsconfig.json不需要新建,输入命令自动生成

tsc --init

然后在tsconfig.json里面将这些改动

"module":"ESNext"	,
"moduleResolution": "node",  
"strict":false//关闭严格模式

package.json:

{
  "type" :"module",
  "name": "storage",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build":"rollup -c"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "rollup": "^3.28.0",
    "rollup-plugin-typescript2": "^0.35.0",
    "typescript": "^5.1.6"
  }
}

rollup.config.js

import ts from 'rollup-plugin-typescript2'
import path from 'path'
import { fileURLToPath } from 'url'
const metaUrl = fileURLToPath(import.meta.url)
const dirName = path.dirname(metaUrl)
export default {
  input: './src/index.ts',
  output: {
    file: path.resolve(dirName, './dist/index.js')
  },
  plugins: [ts()]
}

src/enum/index.ts

//字典 Dictionaries    expire过期时间key    permanent永久不过期
export enum Dictionaries {
    expire = '_expire_',
    permanent = 'permanent'
}

src/type/index.ts

import { Dictionaries } from "../enum"
export type Key = string
export type Expire = Dictionaries.permanent | number //时间戳
export interface Result <T>{
    message: string
    value:T | null
}
export interface Data <T>{
    value: T,
    [Dictionaries.expire]:Expire
}
export interface StorageCls{
    get:<T>(key:Key) => void,
    set:<T>(key:Key,value:T,expire:Expire)=>void
    remove:(key: Key)=> void,
    clear:()=>void,
}

STORAGE/src/index.ts

//expire过期时间key,permanent永不过期
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 = {
            value,//用户传入的value
            [Dictionaries.expire]:expire //过期的时间
        }
        localStorage.setItem(key,JSON.stringify(data))
    }

    get<T>(key: Key):Result<T | null> {
        const value = localStorage.getItem(key)
        if (value) {
            const data: Data<T> = JSON.parse(value)
            //如果是number,那传入的肯定是时间戳,并且需要获取当前时间判断一下,有没有过期
            const now = new Date().getTime()
            if (typeof data[Dictionaries.expire] == 'number' && data[Dictionaries.expire] < now) {  
                this.remove(key)
                return {
                    message: `您的${key}已过期`,
                    value:null
                }

            } else {
                return {
                    message: '成功',
                    value:data.value
                }
            }
        } else {
            return {
                message: '值无效!',
                value:null
            }
        }
     }
    remove(key: Key) {
        localStorage.removeItem(key)
     }
    clear() { 
        localStorage.clear()
    }
 }

TS编写发布订阅模式

概述

什么是发布订阅模式,其实小伙伴已经用到了发布订阅模式例如addEventListener,Vue evnetBus(事件总线)

都属于发布订阅模式

简单来说就是 你要和 大傻 二傻 三傻打球,大傻带球,二傻带水,三傻带球衣。全都准备完成后开始打球。

思维导图

首先 需要定义三个角色 发布者 订阅者 调度者

image-20230821231131619

具体代码

on订阅/监听

emit 发布/注册

once 只执行一次

off解除绑定

interface EventFace {
    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 EventFace {
    lists:List
    constructor() {
        this.lists ={}
    }
    on(name:string,fn:Function) {
        const callback = this.lists[name] || []
        callback.push(fn)
        this.lists[name] = callback
        // console.log(this.lists);
        
    }
    emit(name:string,...args:Array<any>) {
        let eventName = this.lists[name] 
        if (eventName) {
            eventName.forEach(fn => {
                fn.apply(this,args)
            })
        } else {
            console.error(`名称错误${name}`);
            
        }
    }
    off(name: string, fn: Function) {
        let eventName = this.lists[name]
        if (eventName && fn) {
            //通过索引将函数删掉
            let index = eventName.findIndex(fns => fns === fn)
            eventName.splice(index,1)
        } 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',(...args:Array<any>)=>{
    console.log(args,1);
    
})
o.once('post', (...args:Array<any>) => {
    console.log(args,'once');
    
})
// const fn = (...args:Array<any>) => {
//     console.log(args,2);
    
// }
// o.on('post', fn)
// o.off('post',fn)

o.emit('post', 1, false, { name: 'sz' })
o.emit('post',2,true,{name:'sz'})

proxy Reflect

1. proxy

学习proxy对象代理

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

是代理接口的,es6新增语法

target

要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler

一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

handler.get() 本次使用的get

属性读取操作的捕捉器。

handler.set() 本次使用的set

属性设置操作的捕捉器。

案例:

let person = {name:'sz',age:19}
//proxy支持对象 数组 函数 set map
person.name //取值
person.name='ssss'//赋值
let personProxy = new Proxy(person,{
  //拦截取值的操作
  get(){},
  //拦截赋值的操作
  //person name xxx(赋值) person
  set(target,key,value,receiver){
 	return true
  },
  //拦截函数的调用
  apply(){},
  //拦截in操作符
  has(){},
  //拦截for in
  ownKeys(){},
  //拦截new操作符
  construct(){},
  //拦截删除操作
  	deleteProperty(target,p){},
})

案例2:拦截器可以干什么

let person = {name:'sz',age:19}
let personProxy = new Proxy(person,{
  get(target,key,receiver){
    if(target.age<=18){
      //Reflect会直接操作对象,并且参数是一模一样
      return Reflect.get(target,key,receiver)
    }else{
      return 'sz成年了'
    }
  }
})
console.log(personProxy.age)//sz成年了
person.age = 17
console.log(personProxy.age);//17

2.Reflect

//常规对象取值的方式
let person = {name:'sz',age:19}
person.name
//Reflect取值
Reflect.get(person,'name',person)//第三个参数保证上下文的一个正确
//set也是一样的
console.log(Reflect.set(person,'name','sz123',person))
console.log(person)//	 {name:'sz123',age:19}

3.mobx observable

案例

简单的监听的一个模式

//观察者模式
//创建一个存储器
const list:Set<Function> = new Set()

//创建一个订阅函数
const autorun = (cb:Function)=>{
  if(!list.has(cb)){
    list.add(cb)
  }
} //接受一个回掉函数
//提供一个可观测的数据
const observable = <T extends object>(params:T)=>{ //进行一个泛型的约束,因为proxy只支持object引用类型的参数
  return new Proxy(params,{
    //拦截set
    set(target,key,value,receiver){
      //配合Reflect返回一个boolean值
      const result = Reflect.set(target,key,value,receiver)
      //如果数据有变化的时候,通知订阅者
      list.forEach(fn=>fn())
      return result
    }
  })
}
//提供一个可观察的数据
const  personProxy = observable({name:'sz',attr:'先生'})
//订阅
autorun(()=>{
  console.log('有变化了')
})
personProxy.attr='sz威猛先生'//打印有变化了

协变 逆变 双向协变

所谓的类型兼容性,就是用于确定一个类型是否能赋值给其他的类型。typeScript中的类型兼容性是基于结构类型的(也就是形状),如果A要兼容B 那么A至少具有B相同的属性。

1.协变

也可以叫鸭子类型

什么是鸭子类型?

一只鸟 走路像鸭子 ,游泳也像,做什么都像,那么这只鸟就可以成为鸭子类型。

举例说明

//主类型
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:33,
    sex:"女"
}
//只要子类型里面所有的属性可以完全的去覆盖主类型里面所有的属性,那这个赋值是允许的
a = b

A B 两个类型完全不同但是竟然可以赋值并无报错B类型充当A类型的子类型,当子类型里面的属性满足A类型就可以进行赋值,也就是说不能少可以多,这就是协变。

2.逆变

通常发生在一个函数上

//主类型
interface A {
    name:string
    age:number
}
//子类型
interface B {
    name:string
    age:number
    sex:string
}

let fna = (params:A)=>{}
let fnb = (params:B)=>{}
//fna = fnb//这样是不行的
fnb = fna//这样是可以的 安全的
//最后调用fnb函数的时候,执行的是fna

3.双向协变

tsc --init命令

生成tsconfig.json文件

tsconfig strictFunctionTypes 设置为false 支持双向协变 fna fnb 随便可以来回赋值

image-20230822231600055
fna = fnb
fnb = fna

set,map,weakSet,weakMap

1.set

集合是由一组无序且唯一(即不能重复)的项组成的,可以想象成集合是一个既没有重复元素,也没有顺序概念的数组

属性

size:返回字典所包含的元素个数

操作方法

add(value):添加某个值,返回 Set 结构本身。

delete(value):删除某个值,返回一个布尔值,表示删除是否成功。

has(value):返回一个布尔值,表示该值是否为 Set 的成员。

clear():清除所有成员,无返回值。

size: 返回set数据结构的数据长度

let set:Set<number> = new Set([1,2,3,4,6,6,6,6]) //天然去重,引用类型除外
console.log(set)//[1,2,3,4,5,6]
set.add(7)// set 为[1,2,3,4,5,6,7]
set.delete(7)//set为[1,2,3,4,5,6]
set.has(6)//true
set.clear()//[]

2.map

它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

let obj = { name: '小满' }
let map: Map<object, Function> = new Map()
 
map.set(obj, () => 123)
map.get(obj)
map.has(obj)
map.delete(obj)
map.size

3.WeakSet和WeakMap

Weak 在英语的意思就是弱的意思,weakSet 和 weakMap 的键都是弱引用,不会被计入垃圾回收

首先obj引用了这个对象 + 1,aahph也引用了 + 1,wmap也引用了,但是不会 + 1,应为他是弱引用,不会计入垃圾回收,因此 obj 和 aahph 释放了该引用 weakMap 也会随着消失的,但是有个问题你会发现控制台能输出,值是取不到的,应为V8的GC回收是需要一定时间的,你可以延长到500ms看一看,并且为了避免这个问题不允许读取键值,也不允许遍历,同理weakSet 也一样

let obj:any = {name:'小满zs'} //1
let aahph:any = obj //2
//weakmap的key只能是引用类型
let wmap:WeakMap<object,string> = new WeakMap()
wmap.set(obj,'sadsad') //2 他的键是弱引用不会计数的
obj = null // -1
aahph = null;//-1
//v8 GC 不稳定 最少200ms
setTimeout(()=>{
    console.log(wmap)
},500)

Partial & Pick

TS内置高级类型Partial Pick

1.Partial

源码:


/**
 * Make all properties in T optional
  将T中的所有属性设置为可选
 */
type Partial<T> = {
    [P in keyof T]?: T[P];
};

​ 使用前

type Person = {
    name:string,
    age:number
}
 
type p = Partial<Person>

转换后全部转为了可选

type p ={
  name?:string | undefined,
  age?:number | undefined;
}
  • keyof 是干什么的?
  • in 是干什么的?
  • ? 是将该属性变为可选属性
  • T[P] 是干什么的?

1 keyof我们讲过很多遍了 将一个接口对象的全部属性取出来变成联合类型

2 in 我们可以理解成for in P 就是key 遍历 keyof T 就是联合类型的每一项

3 这个操作就是将每一个属性变成可选项

4 T[P] 索引访问操作符,与 JavaScript 种访问属性值的操作类似

2.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 A = {
	text:string;
	age:number;
}
*/
# Record&Readonly

1.Readonly

跟Partial差不多,它将属性变成只读的

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

2.Record

案例:

type Rec<K extends keyof any, T> = {
    [P in K]: T;
};

type key = string | number | symbol

type Person = {
  name:string,
  age:number,
  text:string
}
type K = "A" | "B" | "C"
type B = Rec<K,Person>
let obj:B={
  A:{name:"sz",age:22,text:'男人'},
  B:{name:"sz",age:22,text:'男人'},
  C{name:"sz",age:22,text:'男人'}
}
image-20230827230143787

也可以支持nuber和symbol类型

type K = 1|2|3
let obj:B={
  1:{name:"sz",age:22,text:'男人'},
  2:{name:"sz",age:22,text:'男人'},
  3{name:"sz",age:22,text:'男人'}
}

1 keyof any 返回 string number symbol 的联合类型

2 in 我们可以理解成for in P 就是key 遍历 keyof any 就是string number symbol类型的每一项

3 extends来约束我们的类型

4 T 直接返回类型

做到了约束 对象的key 同时约束了 value.

infer用法

1.条件类型推断案例

infer是ts新增的关键字,充当占位符

来实现一个条件类型推断的例子

定义一个类型 如果是数组类型 就返回 数组元素的类型 否则 就传入什么类型 就返回什么类型

type TYPE<T> = T extends Array<any> ? T[number] : T
 
type A = TYPE<(boolean | string)[]>
 
type B = TYPE<null>

可以通过infer操作来简化

type Infer<T> = T extends Array<infer U> ? U : T
 
type A = Infer<(string | Symbol)[]>

例子2:配合tuple 转换 union 联合类型


type TupleToUni<T> = T extends Array<infer E> ? E : never
 
type TTuple = [string, number];
 
type ToUnion = TupleToUni<TTuple>; // string | number

2.infer类型提取

1.提取头部元素

type Arr = ['a','b','c']
type First<T extends any[]> = T extends [infer one,...arr[]] ?one:[]
type a = First<Arr>//取到的是上面的a,第一个

2.取最后一个

type Arr = ['a','b','c']
type Last<T extends any[]> = T extends [...arr[],infer Last] ?one:[]
type a = Last<Arr>//取到的是上面的a,第一个

3.删掉最后一个元素

type Arr = ['a','b','c']
type pop<T extends any[]> = T extends [...inter Rest,unknow] ? Rest : []
type a = pop<Arr>

4.删除第一个

type Arr = ['a','b','c']
type shift<T extends any[]> = T extends [unknow,...inter Rest] ? Rest : []
type a = shift<Arr>

3.递归

将数组翻转过来

type Arr = [1,2,3,4]
type ReverArr<T extends any[]> = T extends [infer First,...inter rest] ?[...ReverArr<rest>,First] : T//递归操作
type Arrb = ReverArr<Arr>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值