TS学习-3.2更新

364 阅读16分钟

参考: 小满TypeScript基础教程全集(完结)_哔哩哔哩_bilibili

学习TypeScrip1(基础类型)_小满typescript基础教程全集-CSDN博客

介绍安装,基础类型

介绍安装

// 介绍:
js的超集



// 安装
前置环境:node

安装ts
cnpm i typescript -g

检查是否安装,版本号
tsc -v // Version 5.3.3

生成 tsconfig.json 配置文件
tsc --init


创建1.ts
let str: string = 'vv'
console.log(str);

// 第一种方法
编译成js文件
tsc -w // -w即--watch

输出
node 1.js


// 第二种方法
安装ts-node
cnpm i -g ts-node
ts-node 1.ts

基础类型

// string,num,boolean,null,undefined,void,Symbol,bignit

// string
let str: string = "vv"


// num
let num1: number = 123
// NaN,Infinity,十六进制如0xf00d,二进制0010,八进制0o744


// boolean
let boo1: boolean = true
let boo2: boolean = Boolean(0)


// null,undefined
let n: null = null
let un: undefined = undefined

// void空值,多用于无返回的函数
let v1: void = null // 严格模式true报错,false可用
let v2: void = undefined

// void和undefined,null的区别
undefined,null可以赋值给其它类型,void不行

// undefined和null在非严格模式可以互相赋值

基础类型

1. 基本数据类型

Boolean Number String object symbol bigint

let sym: symbol = Symbol("me")
let bi: bigint = 100n // 编译错误,bigint是es11语法,需要配置tsconfig

任意类型:any,unknow

// any,unknow都是顶级类型
let an: any = 1
an = '2'
an = true

let un: unknown = 1
un = '2'
un = true



//  区别1:unknown只能赋值给unknown和any;any可以
let un1: unknown = 1
let un2: string = un1 // error,不能将类型“unknown”分配给类型“string”。ts(2322)
let un3: any = un1


//  区别2:unknow没办法读属性;any可以
let o1: unknown = { age: 1 }
console.log(o1.age); // err,“o1”的类型为“未知”。ts(18046)

// 总结:unknown比any更安全,any的弊端是失去了ts的检测作用

object,Object,{}

// Object
let o1: Object = 1
let o2: Object = '1'
let o3: Object = true
let o4: Object = {}
let o5: Object = []
let o6: Object = () => 1
let o7: Object = null // 不能将类型“null”分配给类型“Object”。ts(2322)
let o8: Object = undefined // 不能将类型“undefined”分配给类型“Object”。ts(2322)


// object,原始类型报错
let o1: object = {}
let o2: object = []
let o3: object = () => 1
 

// {},即new Object等同于Obeject


// 注:
// 为对象直接添加新属性时会报错-->泛型
let o1: Object = {}
o1.age = 1
console.log(o1);

interface:接口和对象类型

// - 1.使用接口约束的时候不能多也不能少属性,必须保持一致
interface Person {
    name: string,
    age: number
}

const vv: Person = {
    name: "vv"
}
// 类型 "{ name: string; }" 中缺少属性 "age",但类型 "Person" 中需要该属性




// - 2.可选,只读
interface Person {
    readonly id: number, // 只读
    name: string,
    age?: number // 可选
}

const vv: Person = {
    id: 1,
    name: "vv"
}

vv.id = 2
// 无法为“id”赋值,因为它是只读属性。ts(2540)



// - 3.任意属性(索引签名)
// 常用:后端接口只取需要字段
interface Person {
    readonly id: number,
    name: string,
    [prop: string]: any
}

const vv: Person = {
    id: 1,
    name: "vv"
}



// - 4.重名,继承
// 重名会合并
// 继承extends
interface A {
    name: string,
}
interface B extends A {
    id: number
}

const vv: B = {
    id: 1,
    name: "vv"
}



// - 5.对象中的函数
interface Obj {
    name: string,
    cb: () => number
}

const o1: Obj = {
    name: "a",
    cb() { return 1 }
}

o1.cb()



// - 6.函数
interface Fn {
    (name: string): number
}

const f: Fn = (name) => 1
f('vv')

数组Array

// - 1.定义
let arr1: number[] = [1, 2, 3]
let arr2: Array<number> = [1, 2, 3]



// - 2.对象数组
interface Aperson {
    name: string,
    [prop: string]: any
}

let arr1: Aperson[] = [{ name: 'a' }, { name: "b" }]



// - 3.二维数组
let arr1: number[][] = [[1], [2]]
let arr2: Array<Array<number>> = [[1], [2]]



// - 4.大杂烩数组
let arr1: any[] = [1, '2', true]

函数

// - 1.定义
function f1(x: number, y: number): number {
    return x + y
}

const f2 = (x: number, y: number): number => {
    return x + y
}



// - 2.默认参数
function f1(x: number, y: number = 20): number {
    return x + y
}



// - 3.可选
function f1(x: number, y?: number): number {
    return x + y // 24年2月25日,严格模式下会报错:“y”可能为“未定义”。ts(18048)
}


// - 4.参数为对象时
interface Obj1 {
    id: number,
    name?: string
}

function f1(ob: Obj1): number {
    return 1
}



// - 5.剩余参数,arguments
function f1(...args: any[]): number {
    console.log(arguments);
    let a: IArguments = arguments
    return 1
}
f1(1, '2', true)
// arguments是伪数组,没有相应方法,用any[]会提示用IArguments



// - 6.函数重构
// 函数名称相同,根据不同参数数量或类型做不同吹
function fn(params: number): number
function fn(params: string): string
function fn(params: string, params2: boolean): number[]

function fn(params: any, params2?: boolean): any {
    if (params2) {
        return [1, 2, 3]
    } else {
        if (typeof params === "number") {
            return 1
        } else {
            return "2"
        }
    }
}
console.log(fn("1", true))

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

// - 1.联合类型
let i: number | string 



// - 2.交叉类型
interface A {
    name: string;
    age: number;
}
interface B {
    sex: string
}

let v: A & B = {
    name: 'v',
    age: 1,
    sex: 'man'
}


// - 3.类型断言as
// 一
const fn = (x: number | string) => x.length 
// err,类型“string | number”上不存在属性“length”。类型“number”上不存在属性“length”。ts(2339)
// 改为
const fn = (x: number | string) => (x as string).length

console.log(fn("123"))

// 二
interface A {
    run: string
}

interface B {
    build: string
}

const fn = (type: A | B): string => {
    return (type as A).run
}


// 三,使用any断言赋值
window.abc = 123 // 类型“Window & typeof globalThis”上不存在属性“abc”。ts(2339)
(window as any).abc = 123

// 注意:类型断言只能欺骗编译器,不会改变结果

内置对象

- 1.内置对象
// String,Number,Boolean,Date,RegExp,Error
let boo1: Boolean = new Boolean(1)


- 2.dom对象,bom对象
// dom对象:常见的HTML(元素名称)Element,HtmlElement,Element
let odiv = document.querySelector('div') as HTMLDivElement
let aDiv: NodeList = document.querySelectorAll('div')

// bom对象:
let loc: Storage = localStorage
let loca: Location = location
let coo: string = document.cookie
let promise: Promise<number> = new Promise<number>((resolve, reject) => {
    resolve(1)
})
问题:下面的代码在JS中可以运行,在TS中会编译错误:参数“x”隐式具有“any”类型
function fn(x, y) {
    return x + y
}
console.log(fn(1, 2)); // 3
console.log(fn('1', '2')); // '12'

思路:
type T = number | string
function fn(x: T, y: T) {
    if (typeof x === 'string' || typeof y === 'string') {
        return x.toString() + y.toString();
    } else {
        return x + y;
    }
}
console.log(fn(1, 2)); // 3
console.log(fn('1', '2')); // '12'

但是仍然存在问题:
const str = fn('aaa','bbb')
str.split('') // TS(2339),类型“number”上不存在属性“split”

解决方法:函数重载
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any { return x + y }

- 1.涉及知识点
// class的基本用法,约束implement,继承extends
// 只读readonly
// private:私有的,只能内部使用,子类和new的实例都无法使用
// protected:内部,子类可用
// public:共有的
// static:静态方法,Vue上的方法,如Promise.all等 注:static只能调用static中的,内部的属性方法
// super原理:指向父类原型链
// get,set方法:类似defineProperty


interface Opt {
    el: string | HTMLElement
}
interface VueCls {
    options: Opt
    init(): void
}

interface Vnode {
    tag: string,
    text?: string,
    children?: Vnode[]
}

class Dom {
    private createElement(el: string) {
        return document.createElement(el)
    }
    protected setText(el: HTMLElement, text: string | null) {
        el.textContent = text
    }
    protected render(data: Vnode) {
        const root = this.createElement(data.tag)
        if (data.children && Array.isArray(data.children)) {
            data.children.forEach(e => {
                const child = this.render(e)
                this.setText(child, e.text ?? null)
                root.appendChild(child)
            })
        } else {
            this.setText(root, data.text ?? null)
        }
        return root

    }
}


class Vue extends Dom implements VueCls {
    constructor(options: Opt) {
        super()
        this.options = options
        this.init()
    }
    readonly options: Opt

    static version() {
        return '1.1.1'
    }
    public init(): void {
        let data: Vnode = {
            tag: "div",
            children: [{
                tag: "section",
                text: "节点1"
            }, {
                tag: "section",
                text: "节点2"
            }, {
                tag: "section",
                text: "节点3"
            },]
        }

        let app = typeof this.options.el == 'string' ? document.querySelector(this.options.el) : this.options.el;
        app?.append(this.render(data))

    }
}

new Vue({
    el: '#app'
})

抽象类(基类)

- 1.抽象类不能实现
abstract class A {
    abstract init() {
        console.log(1); // 方法“init”不能具有实现,因为它标记为抽象。ts(1245)
    }
}
new A() // 无法创建抽象类的实例。ts(2511)

- 2.派生
// 常用于:给其他类继承
abstract class A {
    name: string
    constructor(name?: string) {
        this.name = name as string
    }
    abstract init(name: string): void
    getName() {
        return this.name
    }
}

class B extends A {
    constructor() {
        super()
    }
    init(name: string): void { }
    setName(name: string) {
        this.name = name
    }
}

const bbb = new B()
bbb.setName("vv")
console.log(bbb.getName())

元祖类型Tuple

- 1.定义:数组的变种
const arr: [number, boolean] = [1, true]

- 2.只读
const arr: readonly [number, boolean] = [1, true]
arr[0] = 2 // 无法为“0”赋值,因为它是只读属性。ts(2540)

- 3.可选
const arr: [number, boolean?] = [1]

枚举enum

- 1.数字枚举
// 从0开始
enum Color {
    red,
    green,
    blue,
}
console.log(Color.red, Color.green, Color.blue) // 0,1,2

// 增长枚举
enum Color {
    red = 2,
    green,
    blue,
}
console.log(Color.red, Color.green, Color.blue)  


- 2.字符串枚举
enum Color {
    red = 'red',
    green = 'green',
    blue = 'blue',
}

- 3.异构枚举(不同类型的枚举)
enum Color {
    red,
    green = '1',
}

- 4.接口枚举
interface A {
    name: Color.red
}

enum Color {
    red,
    green = '1',
}

const v1: A = {
    name: 0
}



- 5.const声明枚举
// tsc编译为js文件,const声明的枚举会被编译成常量,普通声明的枚举编译完后是个对象
const enum Type {
    'eat' = 1, 'drink', 'play'
}



- 6.反向映射(由value推key)
enum Type {
    "eat",
    "drink",
    "play",
}

let type = Type[2]
console.log("🚀 ~ type:", type)

类型推断,类型别名

- 1.类型推断
let num = 1 // let num: number
num = '2' // 不能将类型“string”分配给类型“number”。ts(2322)



- 2.类型别名
// 正常使用,定义类型别名
type n = number | string
let num: n = 1
num = '2'
num = true // 不能将类型“boolean”分配给类型“n”。ts(2322)


// 定义函数别名
type n = () => string
let fn: n = () => 'vv'


// 和interface的区别


// 高级用法
// extends:包含, 左边是右边的子类型
type a1 = 1 extends number ? 1 : 0
type a2 = 1 extends Number ? 1 : 0
type a3 = 1 extends any ? 1 : 0
type a4 = 1 extends unknown ? 1 : 0
type a5 = 1 extends Object ? 1 : 0
type a7 = 1 extends never ? 1 : 0 // 0


// 顶级类型知识:any unknow > Object > Number String... > number > 1 true >never

never

- 1.表示不存在的状态



- 2.基本使用
type A = string & number // never

function fn(): never {
    throw new Error("err")
}

function fn2(): never {
    while (true) { }
}



- 3.应用场景
type A = "唱" | "跳" | "rap" | '篮球'

function fn(params: A) {
    switch (params) {
        case "唱":
            break
        case "跳":
            break
        case "rap":
            break
        default:
            // 兜底逻辑
            const error: never = params // type A添加篮球,报错
            break
    }
}

Symbol

- 1.
const v1: symbol = Symbol(1)
const v2: symbol = Symbol(1)
console.log(v1, v2)
console.log(v1 === v2) // false



- 2.
console.log(Symbol.for("v3") === Symbol.for("v3")) // true



- 3.使用场景:用作对象的键名
const v1: symbol = Symbol(1)
const v2: symbol = Symbol(1)

let obj1 = {
    name: "v",
    [v1]: 1,
    [v2]: 2,
}

// 只能拿到name数据
for (const key in obj1) {
    console.log(key)
}
console.log(Object.keys(obj1))
console.log(Object.getOwnPropertyNames(obj1))
// 只能拿到symbol数据
console.log(Object.getOwnPropertySymbols(obj1))
// 解决方法
console.log(Reflect.ownKeys(obj1))



- 4.生成器
function* fn() {
    yield Promise.resolve('v1')
    yield 'v2'
}

const v = fn()
console.log(v) // Object [Generator] {}
console.log(v.next()) // { value: 'v1', done: false }
console.log(v.next()) // { value: 'v2', done: false }
console.log(v.next()) // { value: undefined, done: true }


- 5.迭代器
const s1 = new Set([1, 1, 2, 2])

const m1 = new Map()
m1.set([1,2,3], "v1")

// s1,m1,querySelectorAll,arguments都可以使用for of,∵它们都实现了Symbol.iterator方法

// 手写forof
const each = (value: any) => {
    let n: any = value[Symbol.iterator]()
    let next: any = { done: false }
    while (!next.done) {
        next = n.next()

        if (!next.done) {
            console.log(next.value)
        }
    }
}
// 等价于
for (const iterator of s1) {
    console.log(iterator);
}



- 6.解构
// ...解构的原理也是iterator
let [a, b, c] = [1, 2, 3]



- 7.对象实现Symbol.iterator实现forof
const obj1 = {
    max: 5,
    [Symbol.iterator]() {
        return {
            name: this.name,
            max: this.max,
            next() {
                if (this.max === 0) {
                    return {
                        done: true,
                        value: undefined,
                    }
                } else {
                    return {
                        done: false,
                        value: this.max--,
                    }
                }
            },
        }
    },
}

泛型

- 1.问题引入
// 这样使用函数过于麻烦
function fn1(x: string, y: string): string[] {
    return [x, y]
}

function fn2(x: number, y: number): number[] {
    return [x, y]
}
// 改为
function fn<T>(x: T, y: T): T[] {
  return [x, y]
}
// 或
function fn<A, B>(x: A, y: B): (A | B)[] {
  return [x, y]
}
// 泛型就相当于动态类型



- 2.type和interface使用泛型
type A<T> = number | T
const a: A<boolean> = 1

interface Obj1<T> {
  msg: T
}
const res: Obj1<string | number> = {
  msg: 200,
}



- 3.实际应用:axios
const axios = {
  get<T>(url: string): Promise<T> {
    return new Promise((resolve, reject) => {
      const xhr = 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 {
  code: number
  msg: string
}

axios.get<Data>("./data.json").then((res) => {
  console.log(res)
})



- 4.泛型约束
// 1
interface Obj2 {
  length: number
}
function fn<T extends Obj2>(params: T) {
  return params.length
}

fn("111")
fn([1, 1, 1])
fn(111) // 类型“number”的参数不能赋给类型“Obj2”的参数。ts(2345)

// 2
function fn<T extends number>(x: T, y: T) {
  return x + y
}

fn(1, 2)



- 5.keyof
// 返回对象的一个值
let obj = {
  name: "v",
  age: 11,
}

function rt<T extends object, K extends keyof T>(obj: T, prop: K) {
  return obj[prop]
}

console.log(rt(obj, "age"))
console.log(rt(obj, "sex")) // 类型“{ name: string; age: number; }”上不存在属性“sex”。ts(2339)


// 遍历一个interface将属性加上?可选或者readonly
interface A {
    name: string,
    age:number
}

type Opt<T extends object> {
    [key in keyof T]?:T[key]
}

type B = Opt<A>

tsconfig.json的配置

namespace命名空间

- 1.使用
namespace A {
  export const a = 1
  export const fn = () => 2
}
console.log(A.a, A.fn())

// 可以嵌套写法
// 可以同名合并



- 2.模块化
// 1.ts中export
export namespace A {
  export const a = 1
  export const fn = () => 2
}
// 2.ts中import导入
import {A} from '../1.'



- 3.常见案例:跨端
namespace ios {
  export const fn = () => {
    console.log(1)
  }
}

namespace android {
  export const fn = () => {
    console.log(2)
  }
}

模块解析

- 1.常见
commonjs-->nodejs环境
cmd-->requirejs
amd-->seajs
umd-->amd和commonjs的合体



- 2.es模块化
// 默认导入
export default 1
-
import x from "./1"
console.log(x)


// 分开导入
export const a = 1
export const b = 2
-
import { a, b } from "./1"
console.log(a, b)
// import * as num from "./1" // 或用* as 全部导入


// as起别名


// 动态引入
if (true) {
  import("./1").then((res) => {})
}

声明文件d.ts

import axios from "axios"
import express from "express"

// 在同时安装axios和express时,express提示报错,∵express确少declare声明文件,可npm i @type/包名安装,或自行编写d.ts文件

mixin混入

// 对象的混入
// 类的混入

decorator装饰器

// 类装饰器ClassDecorator
// 装饰器工厂
// 方法装饰器MethodDecorator
// 属性装饰器PropertyDecorator
// 参数装饰器ParameterDecorator

webpack构建v3+ts流程

- 1.
//
tsc --init生成tsconfig.json
npm init -y生成package.json
//
新建webpack.config.js,index.html,src/App.vue和main.ts和shim.d.ts(垫片)
//
tsconfig.js中加上范围include
{
  "compilerOptions": {},
  "include": ["src/**/*"]
}



- 2.配置webpack
//
安装webpack相关依赖: cnpm i webpack webpack-cli -D
安装webpack本地起服务:npm i webpack-dev-sever -D

// 修改package.json
  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server"
  },
  
// 编写webpack.configuration.js,并build打包
const { Configuration } = require("webpack")
const path = require("node:path")
/**
 * @type {Configuration}
 */
module.exports = {
  mode: "development",
  entry: "./src/main.ts",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
}

- 3.支持ts
cnpm i typescript -D
cnpm i ts-loader -D
module.exports = {
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: ["ts-loader"],
      },
    ],
  },
}



- 4.支持vue
cnpm i vue
cnpm i vue-loader -D
cnpm i html-webpack-plugin -D
// main.ts
import { createApp } from "vue"
import App from "./App.vue"
createApp(App).mount("#app")
// index.html
<div id="#app"></div>
// App.vue
<template>1</template>
// 问:怎么把html和main.ts联系起来
const HtmlWebpackPlugin = require("html-webpack-plugin")
const { VueLoaderPlugin } = require("vue-loader")
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: "ts-loader",
      },
      {
        test: /\.vue$/,
        use: "vue-loader",
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./index.html",
    }),
    new VueLoaderPlugin(),
  ],
// 无法识别vue文件,需要垫片
declare module "*.vue" {
  import { DefineComponent } from "vue"
  const component: DefineComponent<[], {}, any>
  export default component
}
// 打包成功



- 5.vue中支持ts
<script setup lang="ts">
</script>
// webpack.config中
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: {
          loader: "ts-loader",
          options: {
            appendTsSuffixTo: [/\.vue$/],
          },
        },
      },
      {
        test: /\.vue$/,
        use: "vue-loader",
      },
    ],
  },
  
  
- 6.样式
cnpm i style-loader -D
cnpm i css-loader -D
cnpm i sass sass-loader -D
{
    test: /\.css$/,
    use: ["style-loader", "css-loader"],
},
{
    test: /\.scss$/,
    use: ["style-loader", "css-loader", "sass-loader"],
},



- 7.优化:模块拆分
// 以安装了moment组件为例
cnpm i moment
app中使用,打包后都挤在一个js文件中
// 修改webpack.config
module.exports = {
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[chunkhash].js",
    clean: true,
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        moment: {
          name: "moment",
          chunks: "all",
          test: /[\\/]node_modules[\\/]moment[\\/]/,
        },
        common: {
          name: "common",
          chunks: "all",
          minChunks: 2, // 引用次数大于2的拆分
        },
      },
    },
  },
}



- 8.不用style-loader
cnpm i mini-css-extract-plugin -D
// webpack
const CssExtractPlugin = require("mini-css-extract-plugin")
module: {
    rules: [
      {
        test: /\.ts$/,
        use: {
          loader: "ts-loader",
          options: {
            appendTsSuffixTo: [/\.vue$/],
          },
        },
      },
      {
        test: /\.vue$/,
        use: "vue-loader",
      },
      {
        test: /\.css$/,
        use: [CssExtractPlugin.loader, "css-loader"],
      },
      {
        test: /\.scss$/,
        use: [CssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./index.html",
    }),
    new VueLoaderPlugin(),
    new CssExtractPlugin(),
  ],

发布订阅模式

已放到设计模式章节中

set map weakSet weakMap

- 1.set
const set: Set<number> = new Set([1, 2, 2, 3, 3, 3])

// add,delete,clear
// set.add(4)
// set.delete(1)
// set.clear()
// console.log(set)

// has
// console.log(set.has(1))

// entries,forEach,forof遍历都可以



- 2.map
const map: Map<any, any> = new Map()

// set,delete,clear
// map.set(1, 11)
// map.set(2, 22)
// map.delete(1)
// map.clear()
// console.log(map)

// map,has
// console.log(map.get(1))
// console.log(map.has(1))

// entries,forEach,forof遍历都可以



/**
 * 3,weakMap,weakSet
 * 
 * 弱引用
 * weakMap,weakSet的键只能是引用类型
 * 会被垃圾回收机制自动回收,V8 GC大约200ms延迟
 */

proxy,Reflect

/**
 * 1,proxy
 *
 * 注:只能代理引用类型,handler对象13种方法,拦截get,set,函数,in,forin等等
 */

/**
 * 2,Reflect
 * 
 * 常用:
 * Reflect.get(target, name, receiver)
 * Reflect.set(target, name,value, receiver) 
 * 注:receiver就相当于target,是处理箭头函数情况下的值
 */

// 演示
const person = { name: "v", age: 10 }

const personProxy = new Proxy(person, {
  get(target, prop, receiver) {
    if (target.age <= 18) {
      const res = Reflect.get(target, prop, receiver)
      return "未成年:" + res
    } else {
      return "成年了"
    }
  },

  set(target, prop, value, receiver): boolean {
    return Reflect.set(target, prop, value, receiver)
  },
})
console.log(personProxy.age)



/**
 * 3,用proxy + Reflect实现vue的Observer函数
 */

const list: Set<Function> = new Set()

const autoRun = (cb: Function) => {
  if (!list.has(cb)) {
    list.add(cb)
  }
}

const observer = <T extends object>(params: T) => {
  return new Proxy(params, {
    set(target, prop, value, receiver) {
      const res = Reflect.set(target, prop, value, receiver)
      list.forEach((e) => e())
      return res
    },
  })
}

const personProxy = observer({ name: "v" })

autoRun(() => {
  console.log("变化了")
})

personProxy.name = "nn"

类型守卫,类型拓宽\类型缩小

/**
 * 1,类型收缩
 *
 * typeof的缺点:对象数组null都返回object
 */

const isStr = (str: any): str is string => typeof str === "string"
const isNum = (num: any): num is number => typeof num === "number"
const isArr = (arr: any) => arr instanceof Array
const isObj = (obj: any) => ({}.toString.call(obj) === "[object Object]")
const isFn = (fn: any) => typeof fn === "function"



/**
 * 2,类型守卫
 *
 * 案例:实现一个函数,传入如何类型
 * 如果是一个对象,就检查里面的属性
 * 属性为number就取两位
 * 属性为string就去除空格
 * 如果是函数就执行
 */

/**
 * 问题1:this的指向出现问题,this.a报错
 * 浏览器环境指向window,node环境指向undefined
 *
 * 问题2:toFixed,trim等代码提示取消了,∵val的类型推断为any
 * 类型守卫:返回值为boolean时
 * const isStr = (str: any): str is string => typeof str === "string"
 * const isNum = (num: any): num is number => typeof num === "number"
 */
const fn = (params: any) => {
  if (isObj(params)) {
    Object.keys(params).forEach((key) => {
      const val = params[key]
      if (isNum(val)) {
        params[key] = val.toFixed(2)
      } else if (isStr(val)) {
        params[key] = val.trim()
      } else if (isFn(val)) {
        params[key]()
      }
    })
  }
}

const obj = {
  a: 10000.2222,
  b: "   v   ",

  c: function () {
    console.log(this)
    return this.a
  },
}

console.log(fn(obj))

协变,逆变

/**
 * 1,协变,鸭子类型
 */
interface A {
  name: string
  age: number
}

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

let a: A = {
  name: "a",
  age: 1,
}

let b: B = {
  name: "b",
  age: 2,
  sex: "man",
}
a = b



/**
 * 2,逆变
 *
 * 看似是反过来的,其实本质上调用的就是fna才是对的
 */
interface A {
  name: string
  age: number
}

interface B {
  name: string
  age: number
  sex: string
}
let fna = (params: A) => {}
let fnb = (params: B) => {}

fnb = fna



/**
 * 3,双向逆变
 *
 *  tsconfig.json中strictFunctionTypes:false
 */

泛型工具一

/**
 * 1,Partial:属性全部可选
 */
// 使用
interface A {
  name: string
  age: number
  sex: string
}

type PartialA = Partial<A>

// type PartialA = {
//   name?: string | undefined
//   age?: number | undefined
//   sex?: string | undefined
// }

// 原理
type myPartial<T> {
    [P in keyof T]?:T[P]
}   

type PartialA = myPartial<A>



/**
 * 2,Required:全选
 */
// 使用
interface A {
  name?: string
  age?: number
  sex?: string
}
type requireA = Required<A>

// 原理
type myRequired<T> {
    [P in keyof T]-?:T[P]
}
type requireA = myRequired<A>



/**
 * 3,Pick:提取部分属性
 */
// 使用
interface A {
  name: string
  age: number
  sex: string
}
type pickA = Pick<A, "age" | "name">


// 原理
type myPick<T, K extends keyof T> = {
  [P in K]: T[P]
}
type pickA2 = myPick<A, "age" | "name">



/**
 * 4,Exclude排除部分属性
 */
// 使用
type excludeA = Exclude<number | string | boolean, string>

// 原理
type myExclude<T, K> = T extends K ? never : T
type excludeA2 = myExclude<number | string | boolean, string>



/**
 * 5,Omit:排除interface中不需要的部分
 */

interface A {
  name: string
  age: number
  sex: string
}
type omitA = Omit<A, "age" | "name">

// 原理:结合Exclude和Pick
type myOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type omitA2 = myOmit<A, "age" | "name">

泛型工具二


/**
 * 1,Record:约束对象的key和value
 *
 * key不能少,value不能少
 *
 * 支持嵌套写法
 */

// 使用
type Key = "c" | "x" | "k"
type Value = "唱" | "跳" | "rap" | "篮球"

const obj: Record<Key, Value> = {
  c: "唱",
  x: "跳",
  k: "rap",
}

// 原理
// 对象的key只能是string number symbol

// 语法糖  string | number | symbol === keyof any
type myRecord<T extends keyof any, K> = {
  [P in T]: K
}

const obj2: myRecord<Key, Value> = {
  c: "唱",
  x: "跳",
  k: "篮球",
}



/**
 * 2,ReturnType:获取函数的返回值
 */

// 使用
const fn = () => [1, true]
type rt = ReturnType<typeof fn>

// 原理
type myReturnType<F extends Function> = F extends (...arg: any[]) => infer Res
  ? Res
  : never
type rt2 = myReturnType<typeof fn>

5. 元祖Tuple

2. 解构

元素较多时,不方便使用下标来访问

let stu: [string, number] = ['xiaobai', 10]
let [user, age] = stu
console.log(user, age); // xiaobai 10

3. 剩余元素

let tu:[string,...number[]]
tu = ['1',2,2,2]

类型断言

有时我们比TS更清楚的知道某个变量的类型,∴我们希望手动指定一个值的类型

const arr: number[] = [1, 2, 3, 4];
const a: number = arr.find(num => num > 2)   // 提示 ts(2322)不能将类型“undefined”分配给类型“number”
const a: number = arr.find(num => num > 2) as number

非空断言

在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。即x!的值不会为null或undefined

let a: number | undefined | null
a.toString() // TS(2533)对象可能为 "null" 或“未定义”
a!.toString()

确定赋值断言

定义了a,没有赋值就使用了,报错

let a:number 
console.log(a); // TS(2534)在赋值前使用了变量“a

!告诉TS这个属性会被赋值

let a!: number
console.log(a); // undefined

字面量类型

// 字符串字面量类型,数字字面量类型,布尔字面量类型
let x: 'string' = 'string';
let y: 1 = 1
let z: true = true;

注意:string类型是马,'string'字面量类型是黑马.所有'string'字面量类型可以给string类型赋值,反之则不行

let str1: 'hello world' = 'hello world'
let str2: string = 'nihao'
str2 = str1
str1 = str2 // TS(2322)不能将类型“string”分配给类型“"hello world"

const,let分析 const定义时,在缺省类型注解时,TS推断出它的类型由赋值字面量类型决定 let定义时,在缺省类型注解时,转换为了赋值字面量类型的父类型--type widening

{
    const str = 'string'; // str: 'this is string'
    const num = 1; // num: 1
    const bool = true; // bool: true
}
{
    let str = 'this is string'; // str: string
    let num = 1; // num: number
    let bool = true; // bool: boolean
}