ts学习

ts学习

基本篇(类型)

01 - ts基本类型

  • 类型 例子 描述
    number 1, -33, 2.5 任意数字
    string ‘hi’, “hi”, 任意字符串
    boolean true、false 布尔值true或false
    字面量 其本身 限制变量的值就是该字面量的值
    any * 任意类型
    unknown * 类型安全的any
    void 空值(undefined) 没有值(或undefined)
    never 没有值 不能是任何值
    object {name:’孙悟空’} 任意的JS对象
    array [1,2,3] 任意JS数组
    tuple [4,5] 元素,TS新增类型,固定长度数组
    enum enum{A, B} 枚举,TS中新增类型
  • number类型

let decimal:number = 6; // 十进制
let hex:number = 0xf00d // 十六进制
let binary:number = 0b1010 // 二进制
let octal:number = 0o744 // 八进制
let big:number = 100n // 大数表示
  • boolean类型
let isDone:boolean = true
let isDone1:boolean = false
  • string类型
let color:string = "blue" // 颜色字符串 blue #ffffff
let fullName:string = `Bob ${color}`
  • 字面量

    • 使用具体的值来指定变量的类型或取值范围

      let color:'red'|'blue'|'black'
      let num:1|2|3|4|5
  • any类型

    • 注意:any具有传染性
let a:any = true
a = 1
a = '1'

unknown类型

let notSure:unknown = 4
notSure = 'hello'

void类型

let unusable:void = undefined

never类型

function error(message:string):never{
throw new Error(message)
}

object类型

let obj:object = {name:"pan"}

array类型

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

tuple类型

let x:[string,number] = ['hello',1]

enum类型

enum Sex{
mail = 1,
femail = 0
}
const sex:Sex = Sex.mail
// 特殊情况
enum FangXiang{
top, // 0
right = 3, // 3
bottom, // 4
left // 5
}
console.log(FangXiang.right) // 3
// 当使用enum初始一个值,后面的内容会依次递增

02 - 类型别名 type 定义

用于一些较长的类型,可以使用type来进行类型别名,避免代码定义类型冗余

type Num = 1 | 2 | 3 | 4
let num1:Num = 1
num1 = 2

03 - interface 接口 定义

interface 和type 一样可以定义类型别名

interface Num {
name:string,
age:number
}
const formData:Num = {
name:'pan',
age:18
}

04 - type和interface的区别

  • 使用场景:

    • type

    使用与一些创建联合类型、交叉类型、以及复杂类型的别名

    type Status = "Success" | "error"
    type ApiResponse = {
    status:Status,
    data: string[]
    }
    function handleResponse = (response:ApiResponse)=>{
    if(response.status === 'Success'){
    console.log("成功!")
    }else{
    console.log("失败!")
    }
    }
    handleResponse({
    status:"Success",
    data:['1','2']
    })interface

    interface

    使用与一些对象的结构的数据定义、类的继承等使用

    interface Obj {
    count:number,
    fn:(n:number)=>number
    }
    const obj:Obj = {
    count:1,
    fn:(n)=>n+1
    }
  • 继承方式

    • type

      type obj1 = {
      name:string
      }
      type obj2 = obj1 & {
      age:number
      }
      // 这样 obj2合并到了obj1的属性
    • interface

      interface obj1 {
      name:string
      }
      interface obj2 extends obj1{
      age:number
      }
      // 这样 obj2继承到了obj1的属性
      1. 继承与合并行为:
      *   `type`支持联合类型、交叉类型和映射类型等高级用法。
      * `interface`支持接口的继承与合并。
      *   `type`可以使用`extends`实现类型的复用。
      * `interface`可以通过`extends`实现接口的继承。
      *   `type`更灵活,可以用于定义任意类型。
      * `interface`更符合面向对象的思想,适用于定义对象和类的结构。
      *   使用`type`当需要创建复杂的类型别名、联合类型等。
      * 使用`interface`当需要定义对象或类的结构。

05 - 类型断言

当自动识别的类型不满足自己的需要时,可以使用类型断言来得到更准确的自己需要的类型

const canvasRrow = document.getElementById("main_canvas")
// 这种情况下得到的类型为 HtmlElement类型

// 第一种写法
const canvasRrow1 = document.getElementById("main_canvas") as HtmlCanvasElement
// 第二种写法
const canvasRrow1 = <HtmlCanvasElement>document.getElementById("main_canvas")
// 这种情况下得到的类型为 HtmlCanvasElement 类型,更满足我们的需求

// 注意:类型断言只使用于缩小范围的类型判断

06 - 文字类型

文字类型使用于对数据进行只读操作,不能进行修改

const obj = {
url:"https://www.baidu.com",
method:"GET",
} as const

function request(url:string,method:"GET" | "POST" | "PUT") {

}
request(obj.url,obj.method)
// 解析:
// 由于request函数第二个参数需要的只要get、post、put这三种类型,但是由于method在obj的内部为string类型,不能进行固定。所以使用as const 来断言,就可以进行method的类型固定不能改变

07 - bigInt和symbol

// bigInt 表示大数
// symbol 表示创建一个唯一值的数
const a:bigint = BigInt(100)
const b:bigint = 100n
// 注意:100n这种写法仅仅支持es2020以上版本
const firstName = Symbol('name')
const secondName = Symbol('name')
// 注意:firstName !== secondName

08 - typeof类型守卫

function sum(n:string | number,input:string){
if(typeof n === 'number'){
return Array(n + 1).join(' ') + input
}else{
return n + input
}
}
console.log(sum("你好",'hello'));

09 - 真值缩小

遇到 条件、&&、||、if语句、布尔否定(!)会产生真值缩小

function printAll(strs:string | string[] | null): void{
// 这行代码中不加strs && 会产生null的typeof也是object类型
// 会进入if判断当中去
if(strs && typeof strs === 'object'){
for(let str of strs){
console.log(str);
}
}else if(typeof strs === 'string'){
console.log(strs);

}else{
//
}
}

10 - 等值缩小

遇到 === 、!== 、== 、!= 的时候会产生等值缩小

// 遇到 === 就会进行类型判断
function example (x:string | number ,y:string | boolean){
if(x === y){
x = x.toUpperCase()
y = y.toLowerCase()
console.log(x,y);
}else{
console.log(x);
console.log(y);
}
}
example("hello","ello");

function example (x:string | number ,y:string | boolean){
if(x === y){
x = x.toUpperCase()
y = y.toLowerCase()
console.log(x,y);
}else{
console.log(x);
console.log(y);
}
}
example("hello","ello");
// 使用stars !== null 可以过滤掉null
function printAll(strs:string | string[] | null){
if(strs !== null){
if(typeof strs === 'object'){
for(const x of strs){
console.log(x)
}
}else if(typeof strs === 'string'){
console.log(strs)
}
}
}

11 - in 操作符缩小

使用in操作符判断是否包含有

type Fish = { swim: () => void };
type Bird = { fly: () => void };
type man = { swim?: () => void, fly?: () => void}
function move(animal: Fish | Bird | man) {
if ('swim' in animal) {
return (animal as Fish).swim();
}
return (animal as Bird).fly();
}

12 - instanceof 操作符缩小

使用instanceof操作符来进行判断 是否在原型链上来进行判断操作

function printAll (x:string | Date){
if (x instanceof Date){
console.log(x.toUTCString());
} else {
console.log(x.toUpperCase());
}
}

13 - 分配缩小

使用三目表达式来进行分配缩小

let x = Math.random() < 0.5?10:''
x = 1
console.log(x);
x = 'hello'
console.log(x);

14 - 类型谓词

在函数返回值时进行判断是否为真还是假进行对应的返回类型

type Fish = {
name:string,
swim:()=>void
}

type Bird = {
name:string,
fly:()=>void
}
// 为真,pet类型为Fish,为假,pet类型为Bird
function isFish(pet:Fish|Bird):pet is Fish{
return (pet as Fish).swim !== undefined
}
function getSmallPet(): Fish|Bird{
let x:Fish = {
name:'fish',
swim:()=>{}
}
let y:Bird = {
name:'bird',
fly:()=>{}
}
return false? y : x
}
const pet = getSmallPet()

if(isFish(pet)){
pet.swim()
console.log('真');

}else{
pet.fly()
console.log("假");

}

15 - unions(联合类型)

interface Square {
type: 'square';
sideLength:number
}
interface Rectangle {
type: 'rectangle';
radius:number
}
type Shape = Square | Rectangle;

function getArea(shape: Shape){
switch(shape.type){
case 'square':
return shape.sideLength ** 2
case 'rectangle':
return Math.PI * shape.radius ** 2
}
}

16 - never 类型

使用范围,穷进行检查,来判断拓展接口进行错误提示

interface Square {
type: 'square';
sideLength:number
}
interface Rectangle {
type: 'rectangle';
radius:number
}
interface sanjiao {
type: 'sanjiao'
sideLength:number
}
type Shape = Square | Rectangle | sanjiao;

function getArea(shape: Shape){
switch(shape.type){
case 'square':
return shape.sideLength ** 2
case 'rectangle':
return Math.PI * shape.radius ** 2
case 'sanjiao':
return shape.sideLength ** 2
default:
const _qiong:never = shape; // 这里会报错,因为不可能执行到这里
return _qiong;
}
}

基本篇(函数)

01 - 函数类型表达式

定义函数类型

type GrandeFunction = (n:string)=>void

function handleFunction(fn:GrandeFunction){
fn('hello world')
}

function helloWorld(n:string){
console.log(n);
console.log('hello world')
}

handleFunction(helloWorld)

02 - 调用签名

其实就是定义了一个函数,在函数身上定义了一个方法

// 定义了函数内部调用时传递str参数返回值boolean类型,
// 函数身上有dirce属性
type dirceFunction = {
dirce:string,
(str:string):boolean
}
function handleDirce(fn:dirceFunction){
console.log(fn.dirce + 'hello');
}
function handle1(){
console.log('handle1');
return true;
}
handle1.dirce = '我的'
handleDirce(handle1)

03 - 构造签名

其实构造签名就是定义了一个类。

// 使用构造签名
class Ctor {
n:string
constructor(n:string){
this.n = n
}
}
type constroller = {
new (n:string):Ctor
}

function handleConstroller(fn:constroller){
const fn1 = new fn('hello')
console.log(fn1.n);
}

handleConstroller(Ctor)
//构造签名和调用签名一起使用
interface DateFunc{
new (n:string):Date
(s:number):string
}
function handleFunc(func:DateFunc){
const a = new func("2024-1-8")
const b = func(100)
}
handleFunc(Date)

04 - 泛型函数

04 - 1- 类型推断

使用场景,使用前不知道会使用什么类型,在使用时,类型之间想要产生关联就使用泛型。

// 单值泛型使用
function handleString<T>(arr:T[]):T{
return arr[0]
}
handleString([1,2,3])

// 多值泛型使用
function map<I,O>(arr:I[],func:(item:I)=>O):O[]{
return arr.map(func)
}
const parsed = map(["1","2","3"],(item)=>parseInt(item))

04 - 2 - 限制条件

使用extends来继承祖代值,来进行限制类型

function longest<T extends {length:number}>(a:T,b:T):T{
if(a.length>b.length){
return a
}else{
return b
}
}
const res1 = longest([1,2,3],[1,2])
const res2 = longest('abc','ab')
const res3 = longest(100,10) // 报错因为number类型没有length类型

04 -3 - 指定类型参数

当泛型需要不只一种可能性参数时使用

function handleTest<T>(a:T[],b:T[]):T[]{
return a.concat(b)
}
// 这里泛型强行指定为string和number类型
handleTest<string|number>([1,2,3],["1","2","3"])

05 - 函数可选值

函数可选值为函数参数上加一个?来判断参数可选

function handleTest(n?:number):undefined |string{ 
if (!(n === undefined)) {
return n.toFixed(3)
}else{
return undefined
}
}
console.log(handleTest(10));
console.log(handleTest());

06 - 回调中的可选参数

在创建回调函数中,不要写可选值,避免错误出现!

// 注意index回调不要写可选值,避免在调用callback时候没传值,在定义callback的时候防止返回undefined
function handleTest(arr:any[],callback:(value:any,index?:number)=>void){
for(let i=0;i<arr.length;i++){
callback(arr[i])
}
}

handleTest([1,2,3],(value,index)=>{
console.log(index?.toFixed())
})

07 - 函数重载

07 - 1 - 函数重载函数情况

使函数有多种传参方式,可以根据自己的参数要求来

注意:在写函数重载时,最后一个重载为前面所有函数参数的一个总和

// 函数为一个参数情况,重载
function makeDate(timemap:number):Date
// 函数为三个参数情况,重载
function makeDate(month:number,day:number,year:number):Date
// 这里表示函数只有两种情况参数传参情况
function makeDate(mOrTimestamp:number,d?:number,y?:number):Date{
if(d!== undefined && y!== undefined){
return new Date(y,mOrTimestamp,d)
}else{
return new Date(mOrTimestamp)
}
}

const res1 = makeDate(100)
const res2 = makeDate(100,1) // 这种情况会发生报错因为没有2个参数情况
const res3 = makeDate(100,1,2020)

注意:前两个函数叫做函数重载,叫做重载签名,后面一个叫做实现签名。实现签名要兼容前面的重载签名情况。

function handleTest(x:string,y:number):string
function handleTest(x:number,y:boolean,z:string[]):number
function handleTest(x:string |number,y:number | boolean,z?:string[]):string | number {
if(typeof x === 'string'){
return 'string'
}else{
return 10
}
}

07 - 2 - 函数重载 - this

interface User {
admin:boolean
}
interface DB{
filterUser(filters:(this:User)=>boolean):User[]
}

const db:DB={
filterUser(filters:(this:User)=>boolean){
let user1:User = {
admin:true
}
let user2:User = {
admin:false
}
return [user1,user2]
}
}

const adminUsers = db.filterUser(function(this:User){
return this.admin
})
console.log(adminUsers);

08 - 展开运算符

08 -1- 展开运算符-形参展开

function handleBei(n:number,...res:number[]):number[]{
return res.map(v=>v*n)
}

handleBei(5,1,2,3,4)

08 -2- 展开运算符-实参展开

// 注意实参展开时,因为使用展开运算符会被推断有多个number类型会发生报错问题,解决问题使用const常量类型
const arr = [8,5] as const
const angle = Math.atan2(...arr)
// 使用元组解决
type Arr = [number,number]
const arr1:Arr = [5,6]
const angle1 = Math.atan2(...arr1)

09 - 参数解构(定义类型)

interface ABC{
a:number
b:number
c:number
}
function sum({a,b,c}:ABC){
return a+b+c;
}
sum({a:1,b:2,c:3});

基础篇(对象)

01 - 对象类型

// 直接添加
function handleObj0(person: { name: string, age: number }){
return person.name
}
// 使用 接口添加类型
interface Person1 {
name: string,
age: number
}
function handleObj1(person: Person1){
return person.name
}
// 使用类型别名进行声明类型
type Person2 = {
name: string,
age: number
}
function handleObj(person: Person2){
return person.name
}

02 - 可选属性

interface Person {
name:string,
age?:number
desc?:string
}

function showPerson({name,age=0,desc=''}:Person){
console.log(name,age,desc);
}

showPerson({name:'张三',age:20})
showPerson({name:'张三',age:20,desc:'学生'})

03 - 只读属性

注意:只读属性readonly只针对当前的属性进行只读操作,并不是对其内部有其内部的限制

interface ReadonlyPerson {
readonly person:{
name:string
age:number
}
}

let persons :ReadonlyPerson = {
person:{
name:"张三",
age:18
}
}
persons.person.age = 18 // 可以更改
persons.person = {name:"李四",age:20} // 不可更改只读属性
interface ReadonlyPerson {
readonly name:string
readonly age:number
}
interface Person {
name:string
age:number
}

let person :Person = {name:'张三',age:18}

// 对其进行赋值,改变person值就可以改变readonlyPerson的值
let readonlyPerson :ReadonlyPerson = person
person.age++
console.log(readonlyPerson.age);

04 - 索引签名

// 相当于定义了数组(数组是特殊的对象)
interface Person1 {
[index:number]:string;
}
let person:Person1 = ['1','2','3']
let person1:Person1 = {0:'1',1:'2',2:'3'}

// 可以进行拓展
interface Person2 {
[prop:string]:string | number;
name:string
length:number
}
let person2:Person2 = {
name:"张三",
length:18,
age:18,
desc:''
}

05 - 拓展类型

// 单个拓展
interface StudentBase {
name:string
age:number
}
interface Student extends StudentBase {
grade:string
}
const student1:Student = {
name:"张三",
age:18,
grade:"一年级"
}
// 多个拓展
interface Circle {
radius:number
}
interface Color {
color:string
}
interface CircleColor extends Circle,Color {
name:string
}
let circleColor:CircleColor = {
name:'circle',
radius:10,
color:'red'
}

06 - 交叉类型

interface Circle {
radius:number
}
interface Color {
color:string
}
type CircleWithColor = Circle & Color

function dorwCircle(dorw:CircleWithColor){
console.log(dorw.color);
console.log(dorw.radius);
}

dorwCircle({radius:1,color:"blue"})

07 - 处理冲突(interface、type)

// 使用重名会进行合并
interface Person {
name:string
}
interface Person {
age:number
}
const person :Person = {
name:"张三",
age:30
}
// 使用重名会进行报错 标识符“Person1”重复。
type Person1 = {
name:string
}
type Person1 = {
age:number
}

08 - 泛型对象类型

使用泛型来对对象进行泛型约束

// 使用接口进行定义
interface Person<T>{
contens:T
}
const res:Person<string> = {
contens:"hello"
}
// 使用类型别名进行定义
type Person1<T> = {
contens:T
}
const res1:Person1<string> = {
contens:"hello"
}

//拓展
type perOrNull<T> = T | null
type perOrMany<T> = T | T[]
// 两者等价
type perOrManyOrNull<T> = perOrMany<T> & perOrNull<T>

type perOrManyOrNull<T> = perOrNull<perOrMany<T>>

基础篇(泛型)

01 - helloWord开始篇章

function putUser<T>(a:T):T{
return a
}

// 手动推断
const a = putUser<number>(1)
// 自动推断
const b = putUser(1)

02 - 使用通用类型变量

function handleloading<T>(arr:T[]):T[]{
console.log(arr.length);
return arr
}
handleloading<number>([1,2,3])

03 - 泛型类型

泛型类型就相当于给接口定义了泛型

// 第一种方法(推荐)
interface GenericIdentityFn<T>{
(arg:T):T
}
// 第二种方法
// interface GenericIdentityFn{
// <T>(arg:T):T
// }


function handleloading<T>(arr:T):T{
return arr
}

这几种都等价
// let myIdentity:(<T>(arr:T)=>T) = handleloading
// let myIdentity:{<T>(arg:T):T} = handleloading

let myIdentity:GenericIdentityFn<string>=handleloading

04 - 泛型类

class Person<T>{
num: T;
add:(x:T,y:T)=>T
}

let p = new Person<number>();
p.num = 123;
p.add = (a,b)=>a+b;

05 - 泛型约束

使用extends来进行对泛型进行约束

interface lengthwise {
length: number;
}

function printLength<T extends lengthwise>(x: T){
console.log(x.length);
}
printLength('10')

06 - 在泛型约束中使用类型参数

这个keyof 表示K必须是在T的泛型中存在的,对应的是key值

function handleTest<T,K extends keyof T>(arr:T,key:K):T[K] {
return arr[key];
}

var obj = {a: 1, b: 2, c: 3};

console.log(handleTest(obj, "a")); // 输出 1
console.log(handleTest(obj, "b")); // 输出 2
console.log(handleTest(obj, "c")); // 输出 3
console.log(handleTest(obj, "d")); // 输出 undefined(未定义

07 - 在泛型中使用类类型

class BeeKeeper {
hasMask: boolean = true
}
class Zookeeper {
nametag: string = "Mikle"
}
class Animal {
numLegs: number = 4
}
class Bee extends Animal {
keeper: BeeKeeper = new BeeKeeper()
}
class Lion extends Animal {
keeper: Zookeeper = new Zookeeper()
}

function create<T extends Animal>(c:new ()=>T):T{
return new c()
}

create(Lion)
create(Bee)
create(Animal)
create(Zookeeper) // 报错 因为没有animal
create(BeeKeeper) // 报错 因为没有animal

基础篇(操作约束)

01 - keyof类型操作符

注意:使用keyof操作索引签名时候,会默认存在一个隐式的number类型

type Point0 = {
x:number,
y:number
}

type A = keyof Point0 // 这个时候A类型为 'x' | 'y'
const a:A = 'x'
const b:A = 'y'

type Point1 = {
[n:number]:unknown
}
type B = keyof Point1 // 这个时候B类型为 number
const c:B = 1
const d:B = 2
type Point2 = {
[n:string]:unknown
}
type C = keyof Point2 // 这个时候C类型为 string | number
const e:C = '1'
const f:C = 1

02 - typeof 类型操作符

// 基本类型使用typeof
let a:number = 1
let b:typeof a = 2

// 函数使用typeof
function fn(){
return {
x:1,
y:'1'
}
}
// 此时的Fn类型会使用fn函数的类型
type Fn = typeof fn
const fn1:Fn = ()=>{
return {
x: 2312,
y: '123'
}
}

03 - 索引访问类型

 type Person = {
name: string;
age: number;
isStudent: boolean;
}
// 类型为 string | number | boolean
type PersonChild = Person[keyof Person]

type NameOrAgePerson = 'age' | 'name'
// 类型为 number | boolean
type I0 = Person[NameOrAgePerson]

const array = [
{name: '张三', age: 18, isStudent: true},
{name: '李四', age: 20, isStudent: false},
{name: '王五', age: 22, isStudent: true},
]

// 类型为{name:string,age:number,isStudent:boolean}
type arraytype1 = typeof array[number]
// 类型为number
type arraytype2 = typeof array[number]['age']

04 - 条件类型

interface Fish {
siwm:boolean
}
interface Bird {
fly:string
}
type HandleTest<T> = T extends {siwm:true} ? string : number

type HandleTest2 = HandleTest<Fish> // string
type HandleTest3 = HandleTest<Bird> // number

05 - 条件类型约束

interface Fish {
siwm:boolean
}
interface Bird {
fly:string
}
type HandleTest0<T extends {siwm:unknown}> = T extends {siwm:true} ? string : number

type HandleTest2 = HandleTest0<Fish>
type HandleTest3 = HandleTest0<Bird> // 不符合要求

type HandleTest1<T> = T extends any[] ? T[number] : T

type HandleTest4 = HandleTest1<string[]> // string
type HandleTest5 = HandleTest1<number[]> // number
type HandleTest6 = HandleTest1<number> // number

06 - 在条件类型中进行推理

// 注意:() =>infer Return 是一个对函数进行输出一个值是Return
type HandleTest0<T> = T extends (...agrs: never[]) => infer Return
? Return
: never;

type Num = HandleTest0<()=>number> // number

type Str = HandleTest0<(x:string)=>string> // string

type Bools = HandleTest0<(a:boolean,b:boolean)=>boolean> // boolean

// 在写函数重载时,最后一个重载为前面所有函数参数的一个总和
function handleTest(x: number): string;
function handleTest(x: string): number;
function handleTest(x: number | string):string | number
function handleTest(x: number | string): string | number {
return Math.random() > 0.5 ? "hello" : 1;
}
type Test = HandleTest0<typeof handleTest>;

07 - 分布式条件类型

对于单个类型进行依次判断

type handleTest1<T> = T extends any?T[]:never
type handleTest2<T> = [T] extends [any]?T[]:never

// string[] | number[] | true[] | false[]
type A = handleTest1<string | number | boolean>
// (string|number|boolean)[]
type B = handleTest2<string | number | boolean>

基础篇(类)

01 - 类成员

类成员分为:类属性、readonly、构造器、方法、Getters/Setters 、索引签名

02- 类属性

class Person {
x:string
y:number
constructor(x:string,y:number){
this.x = x
this.y = y
}
}

let person = new Person("1",1)

03 - readonly属性

class Person {
readonly x:string
constructor(x:string){
this.x = x
}
}
let person = new Person('你好')
console.log(person.x);
person.x = '你好啊' // error

04 - 构造器

class Base {
age:number
constructor(age:number) {
this.age = age
}
}

class Person extends Base {
name:string
constructor(name:string,age:number){
super(age)
this.name = name
}
}
const person = new Person("张三",18)

05 - 方法

class Base {
age:number
constructor(age:number) {
this.age = age
}
getAge(){
return this.age
}
}

const person = new Base(18)

06 - getters和setters访问器

class Base {
_age:number = 0
get age():number{
return this._age
}
set age(value:number | string | boolean){
const num = Number(value)
if(!Number.isFinite(num)){
this._age = 0
return
}
this._age = num
}
}
const person = new Base()
person.age = '20'
console.log(person.age)
person.age = '20a'
console.log(person.age);

07 - 索引签名

class Person {
// 限制类当中的属性或方法类型
[k:string]:string | ((s?:string)=>void)
name:string = 'pansida'
getName(){
console.log(this.name);

}
}

const person = new Person()
person.getName()

08 - 继承 implements子句

这个implements就是相当于定义class实体类时可以使用implements子句来进行给实体类定义类型

// 注意:接口在类中使用就相当于只能单方面限制类的要求,只要类满足接口要求就可以了。
interface Person {
name:string
fn:(n:string)=>void
}

class HandleTest implements Person{
name: string = 'pan'
fn(n:string | number){
if(typeof n === 'string'){
console.log(n.length)
}else{
console.log(n)
}
}
}

09 - 继承 extends子句

class A {
he(){
console.log('A');
}
}

class B extends A {
worf(times:number){
for (let index = 0; index < times; index++) {
console.log('B');
}
}
}

const c = new B();
c.he()
c.worf(5);

10 - 重写方法

// 注意重写方法时,要兼容父类中的方法,不然报错
class A {
he(){
console.log('A');
}
}
class B extends A {
he(times?:string){
if(times === undefined){
super.he();
}else{
console.log('B:',times);

}
}
}
const c = new B();
c.he()
c.he('render');

11 - 初始化顺序

注意(初始化顺序)

  1. 基类的字段被初始化
  2. 基类构造函数运行
  3. 派生类的字段被初始化
  4. 派生类构造函数运行
class A {
name = 'A'
constructor(){
console.log('A中:',this.name);

}
}

class B extends A {
name = 'B'
constructor(){
super()
console.log('B中:',this.name);

}
}

const c = new B();

12 - 继承内置类型

class MyError extends Error {
constructor(message:string){
super(message)
this.message = message;
// 在低版本es5中因为没有class类,访问不到原型身上方法,解决
// 将this指向到MyError.prototype的原型身上
// Object.setPrototypeOf(this, MyError.prototype);
}
SayHello(){
console.log(this.message);

}
}
const myerror = new MyError("Hello");
myerror.SayHello();

13 - 成员的可见性-public

class Person {
// greet 默认情况下有一个public 公开的属性,表示任何地方都可以访问
public greet(){
console.log("hi!");

}
}

class Children extends Person {
constructor(){
super()
// 这里也可以访问到greet方法
this.greet()
}
}

const child = new Children()
child.greet() // 这里也可以访问到greet方法

14 - 成员的可见性-protected

class Person {
public name = 'pan'
// protected 表示受保护的,在自身类中和子(后代)类中可以访问
protected SaiHello(){
console.log("Hello");

}
}

class Children extends Person {
constructor(){
super()
// 子类可以访问到
this.SaiHello()
}
}

const child = new Children()

//这对M类中的name进行重写,所以可以访问
class M {
protected name = 'pan:M'
}
class N extends M {
public name = 'pan:N'
}

const test = new N()
console.log(test.name);

15 - 成员的可见性 - privide

class Person {
private name = 'pan'
// private 表示私有的,在自身类中可以访问
public SaiHello(){
console.log(this.name);
}
}

class Children extends Person {
constructor(){
super()
this.SaiHello()
// this.name = 'pan1' // 报错
}
}

const child = new Children()
// child.name = 'pan2' // 报错


// 这种情况下允许跨实例进行访问
class A {
private name = 'A'
public getName(n:A){
return n.name === this.name
}
}

const a1 = new A()
const a2 = new A()
a1.getName(a2)

16 - 静态成员

注意:

特殊的静态名称不安全,避免使用:name、length、call等

Typescript中没有静态类的概念,因为js中还有函数和普通对象表示

class Person {
// static 静态方法,就是可以不用通过创建实例,直接通过类调用
static a = 1
// 静态方法身上可以使用成员属性的
protected static b = 1
static SayHello(){
console.log('hi');

}
}
class Children extends Person {
constructor(){
super()
console.log(Person.b);
}
}

Person.SayHello(); // 直接通过类名调用静态方法

17 - static区块

注意:static区块和constructor的区别:

static区块可以在实例没创建之前就可以调用,而constructor要在创建实例才调用

//静态块允许您编写具有自己作用域的语句序列,这些语句可以访问包含类中的私有字段。这意味着我们可以编写具有编写语句的所有功能的初始化代码,不会泄漏变量,并且可以完全访问我们类的内部结构。

class Person {
static #count = 0;
get count(){
return Person.#count;
}
static {
const lastInterce = {length:100}
Person.#count+=lastInterce.length
}
}

const person = new Person();
console.log(person.count);

18 - 泛型类

class Box<T> {
contents:T
constructor(value:T) {
this.contents = value
}
// 注意staic属性是不能使用泛型的
static id:T // 报错
}
// 写法
const box = new Box<string>('Hello World')
const box1:Box<string> = new Box('Hello World')
const box2 = new Box('Hello World')

19 - 类运行中的this

class Box {
name = 'Box'
getName = ()=>{ // Box
return this.name
}
getName(){ // obj
return this.name
}
}

const box = new Box()

const obj = {
name:'obj',
getName:box.getName
}
console.log(obj.getName())

20 - this类型

// 这个实例中由于传递base改变了this的指向,在DeriveBox的指向变为了Box实例,又由于Box中没有没有otherContent属性,所以会报错
class Box {
content:string = ''
sameAs(ohter:this){
return ohter.content === this.content
}
}

class DerivedBox extends Box {
otherContent:string = '?'
}

const base = new Box()
const derived = new DerivedBox()
derived.sameAs(base) // 报错

21 - 基于类型守卫的this

class FileSystemObject {
isFile():this is FileRep{
return this instanceof FileRep;
}
isDirectory():this is Directory{
return this instanceof Directory;
}
isNetWorked():this is NetWorked & this{
return this.NetWorked
}
constructor(public path:string,private NetWorked:boolean){

}
}

class FileRep extends FileSystemObject{
constructor(path:string,public content:string){
super(path,false);
}
}
class Directory extends FileSystemObject {
children:FileSystemObject[]
constructor(){
super('',true);
this.children=[];
}
}
interface NetWorked {
host:string
}

const fso:FileSystemObject = new FileRep('/index.txt','foo')
if(fso.isFile()){
//fso 是一个FileRep
fso.content
}else if(fso.isDirectory()){
//fso 是一个Directory
fso.children
}else if(fso.isNetWorked()){
//fso 是一个NetWorked
fso.host
}

class Box<T>{
value?:T
hasValue():this is {value:T}{
return this.value !== undefined;
}
}
const box:Box<string> = new Box()
box.value = 'hello'
if(box.hasValue()){
console.log(box.value);

}

22 - 参数属性

class Person {
// 在constructor身上方法的属性就是参数属性
constructor(public name:string,protected age:number,private gender:string){
this.name=name;
this.age=age;
this.gender=gender;
}
}

23 - 类表达式

// 类表达是就相当于定义了一个匿名类使用变量进行接收
const someName = class<T>{
name:string
constructor(value:string){
this.name = value
}
}

const n = new someName('123')
console.log(n.name);

24 - 抽象类和成员

// 抽象类不能被创建实例
abstract class Base {
// 定义抽象成员,不能在其中实现
abstract getName():string
SayHello(){
console.log(`Hello ${this.getName()}`);

}
}
class Child extends Base {
// 子类中实现抽象成员方法
getName(){
return "Child"
}
}

const child = new Child()
console.log(child.getName());
child.SayHello();

// 抽象构造签名
function HandleTest(Constor:new ()=>Base){
const a = new Constor()
console.log(a.getName());
a.SayHello()
}
HandleTest(Child)

25 - 类之间的关系

// 类之间关系
class A1{
name:string ='pan'
age:number = 18
}
class A2{
name:string ='xiaopan'
age:number = 20
}

// 类之间只要类型一样就可以互相使用类型
const a1:A2 = new A1()

// 空类其他都符合要求
class Test {

}
function fn(test:Test){

}

fn(window)
fn(document)
fn(100)

基础篇(小满扩展)

01 - Object、object、{}的区别

// 在TS中Object为原型链的顶端包含所有类型
let a1:Object = 1
let a2:Object = "1"
let a3:Object = []
let a4:Object = {}
let a5:Object = new Date()
let a6:Object = ()=>12
// object使用于类型中,表示非指向类型,一般用于泛型约束
let a:object='123'//错误原始类型
let a1:object= 123//错误原始类型
let a2:object= true//错误原始类型
let a3:object=[]//正确
let a4:object={}//正确
let a5:object=()=>123//正确
// {} 使用类似于new a() 包含所有类型和Object类型
let a:{}='123'
let a1:{}= 123
let a2:{}= true
let a3:{}=[]
let a4:{}={}
let a5:{}=()=>123
let b:{} = {name:123} //使用此类型不能向类型上添加属性
b.age = 1

02 - 后端接口怎么定义

// 当使用后端接口时,只需要其中一些属性,其他属性不想定义太多了
interface A{
name:string
age:number
[key:string]:any
}
const res:A = {
name:'xiaopan',
age:123,
f:0
}
//调用arguments对象时,使用IArguments类型
function handleTest(...agrs:number[]){
const a:IArguments = arguments
}

03 - 在对象中定义this

interface A {
user:string[],
fn:(this:A,a:string)=>void
}

let res:A = {
user:['pan','xiao'],
// 此时this作为第一个参数会不作为参数调用
fn(this:A,a:string){
this.user.push(a)
}
}

res.fn("1"); // 此时this会不作为参数

04 - 常用的一些的内置类型

// 01. 一些内置类的类型

let a:Number = new Number(1)
let b:String = new String('hello')
let time:Date = new Date()
let regExp:RegExp = new RegExp('\\w+') // 内置正则
let error:Error = new Error('Error occurred')
let xml:XMLHttpRequest = new XMLHttpRequest()

// 02. dom类型

// HTML(元素)Element
const dom = document.querySelector('div')
//HTMLElement
const dom2 = document.querySelector('footer')
// dom数组(单样的)
const doms1:NodeList = document.querySelectorAll('div')
// dom数组(多样的)
const doms2:NodeListOf<HTMLDivElement | HTMLElement> = document.querySelectorAll('header')

// 03.bom类型
const local:Storage = localStorage
const cookie:string = document.cookie
const lo:Location = location
//注意:Promise中泛型和返回值保持一致'done'
const promise:Promise<string> = new Promise((res)=>res('done'))

05 - vue简写版

// 使用ts来模仿vue和虚拟dom架构

// vue配置
interface Options{
el:string | HTMLElement;

}

// vue类中方法
interface Vuecls{
options:Options;
init():void;
}
// 虚拟dom
interface Vnode{
tag:string
text?:string
children?:Vnode[]
}

// 虚拟dom(简洁版)
class Dom {
// 创建dom节点
private createElement(el: string){
return document.createElement(el);
}
// 填充文本
private setText(el:HTMLElement, text:string | null){
el.textContent = text;
}
// 渲染函数
render(data:Vnode){
// 获取节点
let root = this.createElement(data.tag);
// 判断当前节点是否有子节点
if(data.children && Array.isArray(data.children)){
// 有,继续渲染
data.children.forEach(item=>{
// 渲染
let child = this.render(item)
// 将当前子节点添加到父节点身上去
root.appendChild(child)
})
}else{
// 没有子节点进行填充文本
this.setText(root, data.text as string);
}
// 返回当前节点
return root
}
}

// vue(简洁版)
class Vue extends Dom implements Vuecls{
options: Options;
constructor(options: Options){
super()
this.options = options;
this.init();
}
init():void{
// 虚拟dom,通过js去渲染我们的真是dom
let data:Vnode = {
tag:'div',
children:[
{tag:'div',text:'hello'},
{tag:'div',text:'world'}
]
}
// 获取当前绑定节点
let app = typeof this.options.el === 'string' ? document.querySelector(this.options.el) : this.options.el;
// 给节点进行渲染dom
app?.append(this.render(data))
}
}

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

06 - const枚举

// 使用const枚举
const enum Tyeps {
success,
fail
}
const res:number = 0
if(res === Tyeps.success){

}
//编译后
"use strict";
const res = 0;
if (res === 0 /* Tyeps.success */) {
}

// 不使用const枚举
enum Tyeps {
success,
fail
}
const res:number = 0
if(res === Tyeps.success){

}
//编译后
"use strict";
var Tyeps;
(function (Tyeps) {
Tyeps[Tyeps["success"] = 0] = "success";
Tyeps[Tyeps["fail"] = 1] = "fail";
})(Tyeps || (Tyeps = {}));
const res = 0;
if (res === Tyeps.success) {
}

07 - 反向映射枚举

// 反向映射只能用于枚举值为number类型,为其他类型报错
enum Types{
success= 2312
}
const success = Types.success // 读取2312值
const type = Types[2312] // 读取'success'值
console.log(`type:${type}`,`success:${success}`);

08 - 类型层次

ts类型区分.png

09 - Symbol类型

// Symbol类型就是创建唯一值使用的,一般作为对象key值使用
const num1:symbol = Symbol("1")
const num2:symbol = Symbol("1")
console.log(num1 === num2);
// Symbol的for方法是指,在全局中进行查找,有就不创建直接使用,没有就进行创建
console.log(Symbol.for("1") === Symbol.for("1"));

10 - 迭代器/生成器

// 01.生成器
function* fn(
yield 1
yield 2
yield 3
yield 4
yield 5
)
let g = fn()
g.next()
g.next()
g.next()

//02.迭代器
// set map
const set = new Set([1, 2, 3, 4, 5,1,2,4]); // 天然去重

const map = new Map() // 作用可以将其他例如数组类型当作key值
const obj = [1,2,3]
map.set(obj,1)

//03.自定义迭代器
const each = (value:any)=>{
let It:any = value[Symbol.iterator]()
let next:any = {done:false}
while(!next.done){
next = It.next()
if(!next.done){
console.log(next.value);
}
}
}

//0.4迭代器语法糖是 for of
// 注意:对象身上没有Symbol.iterator语法的
for(let i of obj){
console.log(i)
}

// 05.解构底层也是使用的是iterator
let [a,b,c] = [1,2,3]


// 06.手动实现对象使用for of
const obj3 = {
max:5,
current:0,
[Symbol.iterator](){
return{
current:0,
max:this.max,
next(){
if(this.current === this.max){
return{
value:undefined,
done:true
}
}else{
this.current++;
return{
value:this.current,
done:false
}
}
}
}
}
}

for(let i of obj3){
console.log(i);

}

11 - 泛型封装axios(简易版)

const axios = {
get<T>(url:string):Promise<T>{
return new Promise((resolve,reject)=>{
let xml:XMLHttpRequest = new XMLHttpRequest()
xml.open('GET',url)
xml.onreadystatechange = ()=>{
if(xml.readyState === 4 && xml.status === 200){
resolve(JSON.parse(xml.responseText))
}
}
xml.send(null)
})
}
}

interface Data{
message:string,
code:number
}

axios.get<Data>('./db.json').then(res=>{
console.log(res);
})

12 - keyof高级用法

interface Obj {
name:string,
age:number,
dec:string
}
// 将Obj变成可选参数类型
type Options<T extends Obj>= {
[key in keyof T]?:T[key]
}
type B = Options<Obj>

13 - ts配置文件

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

14 - namespace命名空间

// namespace作用:分类进行书写接口,防止类型全局污染
// 用法:嵌套、抽离、导出、简化、合并
// 案例:假如要写接口对应不同的平台:H5、小程序、android、ios
namespace ios {
export let a = 1
}
namespace android {
export let b = 2
export namespace android_inter{
export let c = 3
}
}

// 使用
console.log(ios.a);
console.log(android.b);
console.log(android.android_inter.c);

15 - 三斜线指令

/// <reference path="..." />指令是三斜线指令中最常见的一种。 它用于声明文件间的 依赖。

// 在index1.ts中
namespace A{
export let a = 1
}
// 在index.ts中
/// <reference path="./index1.ts" />
/// <reference path="./index1.ts"/>
namespace A {
export let x = 1;
export function f() {}
}
console.log(A.a);
/// <reference types="node" />引入到声明文件,表明这个文件使用了 @types/node/index.d.ts里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。
//仅当在你需要写一个d.ts文件时才使用这个指令。
// 使用第三方插件时使用声明文件需要下载
// npm i @types/包名
// 例如:使用node的报名 npm i @types/node
///<reference types="node" />

16 - 声明文件(包名.d.ts)

有一些第三方没有自己相对应的包名声明文件。以下为解决方案:

  1. 使用社区提供的声明文件进行下载,下载

    @types/包名 // 例如:@types/node

  2. 下载404使用第二种方法,进行手动编写声明文件

    // 创建声明文件的名字为:包名.d.ts
    // 使用declare来进行声明
    declare module '包名' {
    exprots default 导出内容
    }

以下为一个以express的实例

// express.d.ts
declare module 'express' {

interface App {
use:(path:string,router:Router)=>void
listen:(port:number,cb:()=>void)=>void
}

interface Router {
get(path:string,cb:(req:any,res:any)=>void):void
}

interface Express {
():App,
Router():Router
}
const express: Express;
export default express
}

17 - Mixin混入

将多个类或对象等混入在一起

//对象混入
interface A {
name:string
}

interface B {
age:number
fn:()=>void
dec:Dec
}
type Dec = [number,number,{name:string}]

const res1:A = {
name:'pan'
}

const res2:B = {
age:18,
fn:()=>{console.log('fn')},
dec:[1,2,{
name:'pan',
}]
}

//进行混合

//使用es6语法混合但是是浅拷贝
const res3 = {...res1,...res2}
const res4 = Object.assign({},res1,res2)
// structuredClone这个是直接进行深拷贝,但是仅支持node18以上版本
const a = structuredClone(res1)

// 插件类型混入
class logger {
log(msg:string){
console.log(msg);
}
}

class Html {
render(res:string){
console.log(res);

}
}

class App {
run(){
console.log("run");
}
}

type Constructor<T> = new (...args: any[]) => T;

function Mixins<T extends Constructor<App>>(Base:T){
return class extends Base{
private logger:logger;
private html:Html;
constructor(...args: any[]){
super(...args)
this.logger = new logger();
this.html = new Html();
}
run(): void {
this.logger.log('hello')
}
render(){
this.logger.log('hello')
this.html.render('<h1>hello</h1>');
}
}
}

const mixins = Mixins(App)

const app = new mixins()
app.run()
app.render()

18 - 装饰器(Decorator)

类装饰器(ClassDecorator)

就是对class类进行拓展修饰

// 不传参
// 这个target为Http类
const Base:ClassDecorator = (target)=>{
// 给Http类添加属性name
target.prototype.name = '123';
// 给Http类型添加属性fn方法
target.prototype.fn = ()=>{
console.log('fn');

}
}

@Base
class Http{

}
// 传参(利用闭包形式传参)
const Base = (str: string) => {
const fn: ClassDecorator = (target) => {
target.prototype.name = "123";
target.prototype.fn = () => {
console.log("fn",str);
};
};
return fn;
};

@Base("pan")
class Http {}

方法装饰器 (MethodDecorator)

就是对类中方法进行修饰

import axios from 'axios';
const Get = (url:string)=>{
const fn:MethodDecorator = (target,key,descriptor:PropertyDescriptor)=>{
axios.get(url).then(res=>{
descriptor.value(res.data)
})
}
return fn
}


class Http {

@Get('https://api.apiopen.top/api/getHaoKanVideo?page=0&size=10')
getList(data:any){
console.log(data);
}


}

参数装饰器(ParameterDecorator)

对参数进行修饰

// 引入反射方法
import 'reflect-metadata'
import axios from 'axios';
const Get = (url:string)=>{
const fn:MethodDecorator = (target,key,descriptor:PropertyDescriptor)=>{
// 获取反射中保存的值
const key1 = Reflect.getMetadata('key',target)
axios.get(url).then(res=>{
// 判断key1是否存在
descriptor.value(key1?res.data[key1]:res.data)
})
}
return fn
}

const Result = ()=>{
// index为该参数的位置
const fn:ParameterDecorator = (target,key,index)=>{
console.log(target,key,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);
}


}

基础篇(构建TS项目)

01 - Rollup构建TS项目

安装依赖
1.全局安装rollup npm install rollup -g
2.安装TypeScript npm install typescript -Ds
3.安装TypeScript转换器npm install rollup-plugin-typescript2 -D
4安装代a码压缩插件npm install rollup-plugin-.terser -D
5安装rollup web服务 npm install rollup-plugin-seve -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

// rollup.config.dev.js
import path from "path"
// ts转换器
import ts from "rollup-plugin-typescript2"
// 热更新
import livereload from "rollup-plugin-livereload"
// 开启前端服务器
import serve from "rollup-plugin-serve"
// 浏览器使用环境变量
import replace from "rollup-plugin-replace"

export default {
input: './src/index.ts',
output:{
file: path.resolve(__dirname,'../lib/index.js'),
format:'cjs',
sourcemap:true
},
plugins:[
ts(),
livereload(),
serve({
open:true,
openPage:'/public/index.html',
port:1988
}),
replace({
'process.env.NODE_ENV':JSON.stringify(process.env.NODE_ENV)
})

]

}
// rollup.config.prod.js
import path from "path"
// ts转换器
import ts from "rollup-plugin-typescript2"
// 代码压缩
import { terser } from "rollup-plugin-terser"
// 浏览器使用环境变量
import replace from "rollup-plugin-replace"

// 判断当前环境
const isDev = () => process.env.NODE_ENV === 'development'
export default {
input: 'src/index.ts',
output:{
file: path.resolve(__dirname,'../lib/index.js'),
format:'cjs',
sourcemap:true
},
plugins:[
ts(),
terser({
compress:{
drop_console:!isDev()
}
}),
replace({
'process.env.NODE_ENV':JSON.stringify(process.env.NODE_ENV)
})

]

}
// package.json
{
"name": "rollup_ts",
"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 ./config/rollup.config.dev.js -w",
"build": "cross-env NODE_ENV=production rollup -c config/rollup.config.prod.js"
},
"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"
}
}

02 - webpack构建TS项目(简单搭建构架)

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
/**
* @type {import('webpack').Configuration}
*/
const config = {
entry: './src/index.ts',
mode:'development',
output:{
path:path.resolve(__dirname, 'dist'),
filename:'bundle.js'
},
resolve:{
extensions: ['.ts', '.js']
},
module:{
rules:[
{
test: /\.ts/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
plugins:[
new HtmlWebpackPlugin({
template: './public/index.html'
})
]
};

module.exports = config;

//package.json
{
"name": "webpack_ts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev":"webpack-dev-server",
"build":"webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"html-webpack-plugin": "^5.6.0",
"ts-loader": "^9.5.1",
"typescript": "^5.3.3",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
}
}

03 - esbuild+swc构建TS项目

介绍:

esbuild是go语言编写的并且是多线程执行,性能是js的好几十倍,所以很快。

  1. 无需缓存即可实现基本打包
  2. 支持ES6跟CommonJS模块
  3. 支持ES6 Tree Shaking
  4. 体积小
  5. 插件化
  6. 其他
  7. 内置支持编译JSX

esbuild.png

swc则宣称其比Babel快20倍(四核情况下可以快70倍)

swc.png

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

// 配置config.ts
import esbuild from 'esbuild'; //打包工具
import swc from '@swc/core'; //js编译器 类似于es6转es5
import fs from "node:fs"
await esbuild.build({
// 设置入口
entryPoints: ['src/index.ts'],
// 配置loader编译器
loader:{
'.ts': 'ts',
'.js': 'js',
'.jsx': 'jsx',
'.tsx': 'tsx'
},
//消除console.log()等打印部分
treeShaking:true,
// 设置当前环境
define:{
'process.env.NODE_ENV': '"production"',
},
//配置插件
plugins:[
//使用swc编译器
{
name:"swc-loader",
setup(build){
//加载对应文件
build.onLoad({filter:/src\/.(js | ts | tsx | jsx)$/},(args)=>{
// 读取当前文件
const content = fs.readFileSync(args.path,'utf-8')
//使用swc进行打包起名
const {code} = swc.transformSync(content,{
filename:args.path
})
// 输出内容
return {
contents:code
}
})
}
}
],
outdir:'dist'
})

注意:在package.json中加上type:”module”

启动指令:ts-node-esm config.ts

04 - TS编写发布订阅模式

发布订阅模式.png

具体代码:on订阅/监听 emit发布/注册 once只执行一次 off解除绑定

interface List {
[key: string]: Function;
}

interface EventFn {
on(name: string, fn: Function): void;
emit(name: string, ...agrs: any[]): void;
off(name: string): void;
once(name: string, fn: Function): void;
}

class Dispatch implements EventFn {
private list: List;
constructor() {
this.list = {};
}
on(name: string, fn: Function) {
this.list[name] = fn;
}
emit(name: string, ...agrs: any[]) {
if (this.list[name]) {
this.list[name].apply(this, agrs);
} else {
console.error(`名称错误${name}`);
}
}
off(name: string) {
if (this.list[name]) {
delete this.list[name];
} else {
console.error(`名称错误${name}`);
}
}
once(name: string,...agrs: any[]) {
if (this.list[name]) {
this.emit(name,...agrs);
this.off(name);
} else {
console.error(`名称错误${name}`);
}
}
}
export default new Dispatch();

05 - set、map、weakset、weakmap使用

  1. set和map
const set:Set<number> = new Set([1, 2, 3, 4, 4]); // 天生自带去重
set.add(5) // 添加方法
set.delete(1) // 删除方法
set.clear() // 清空方法
set.has(1) // 判断是否包含某个元素
set.forEach // 可以进行遍历的
set.entries() // 返回键值对的遍历器
set.keys() // 返回键的遍历器

const obj = {name:"pan"}
const map:Map<Object,any> = new Map() // map特点就是key值可以为引用类型

map.set(obj,"asnfa") // 添加方法
map.delete(obj) // 删除方法
map.has(obj) // 判断是否包含某个元素
map.clear() // 清空方法
map.entries() // 返回键值对的遍历器
map.keys() // 返回键的遍历器
  1. weakset 和weakmap

注意:weakset 和weakmap为弱引用不会被计入垃圾回收机制的

let obj:any = {name:'pansida'} // +1

const weakmap:WeakMap<Object,any> = new WeakMap()

weakmap.set(obj, '123') // 不计入次数,弱引用
obj = null // -1
console.log(weakmap.get(obj)) // 此时为0,被垃圾回收机制释放了,返回undefined

let arr:any = [1,2,3,4] // +1

const weakset:WeakSet<any> = new WeakSet()
weakset.add(arr) // 不计入次数,弱引用
arr = null // -1
console.log(weakset.has(arr)) // 此时为0,被垃圾回收机制释放了,返回undefined

06 - proxy和Reflect

proxy就是数据劫持

const obj = { name: "xiaopan" };

const objProxy = new Proxy(obj, {
// 获取数据
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
// 改变数据
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
},
// 删除数据
deleteProperty(target, key) {
return Reflect.deleteProperty(target, key);
},
// 使用in操作符
has(target, key){
console.log("我触发了");
return Reflect.has(target, key);
},
// for in 操作符触发
ownKeys(target){
return Reflect.ownKeys(target);
},
// 函数调用时触发
apply(target, thisArg, argArray){

},
// 使用new调用时触发
construct(target,thisArg,argArray){
// return Reflect.construct(target,thisArg,argArray);
return {}
}
});

if('xiaopan' in objProxy){
console.log(111);
}

// 使用proxy实现观察者模式

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

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


let observable = <T extends object>(param:T)=>{
return new Proxy(param,{
get(target,value){
return Reflect.get(target,value)
},
set(target,value,newValue){
list.forEach(fn=>fn())
return Reflect.set(target,value,newValue)
}
})
}

autorun(()=>{
console.log('我被改变了');

})

const obj = observable({name:'张三',age:20})

obj.name = '李四'

07 - 类型守卫(面试题)

// 面试题
// 实现一个函数,该函数可以传入任何类型
// 但是如果时object 就检查里面的属性,如果里面的属性是number就取两位
// 如果是string就去除左右空格
// 如果是函数就执行

const isObject = (data:any)=> ({}).toString.call(data) === '[object Object]';
const isnumber = (data:any):data is number => typeof data === 'number';
const isstring = (data:any):data is string => typeof data === 'string';
const isfunction = (data:any):data is Function => typeof data === 'function';

function handleTest(data:any){
if(isObject(data)){
Object.keys(data).forEach(index=>{
let val = data[index]
if(isnumber(val)){
data[index] = val.toFixed(2)
}
if(isstring(val)){
data[index] = val.trim()
}
if(isfunction(val)){
data[index]()
}
})
}
}

const obj = {
a:10.3412,
b:' hello world ',
c:function(){
console.log(this);
console.log('hello function')
}
}

handleTest(obj)

08 - 协变、逆变、双向协变 (类型兼容性)

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

// 协变
// 主类
interface A {
name:string,
age:number
}
// 子类
interface B {
name:string,
age:number,
gender:string
}

let a:A = {name:'a', age:18}
let b:B = {name:'b', age:18, gender:'male'}
a = b

//逆变
let fnA = (p:A)=>{};
let fnB = (p:B)=>{};
fnB = fnA;

// 双向协变 需要开启"strictFunctionTypes": false
fnB = fnA
fnA = fnB

09 - 泛型工具

Partial属性可选的意思

Required属性必选的意思

Pick提取部分属性

Exclude排除部分属性

Omit排除部分属性并且返回新的类型

Record 约束对象key以及value

ReturnType 获取函数类型的返回值

  1. Partial

    interface A {
    name: string;
    age: number;
    gender: string;
    }

    //Partial 使用
    type PartialA = Partial<A>;
    //使用后效果
    // interface PartialA {
    // name?: string;
    // age?: number;
    // gender?: string;
    // }
    //实现原理
    type PartialATest<T> = {
    [P in keyof T]?: T[P]
    }
  2. Required

    interface A {
    name?: string;
    age?: number;
    gender?: string;
    }

    //Partial 使用
    type PartialA = Required<A>;
    //使用后效果
    // interface PartialA {
    // name: string;
    // age: number;
    // gender: string;
    // }
    //实现原理
    type PartialATest<T> = {
    [P in keyof T]-?: T[P]
    }
  3. Pick

    interface A {
    name?: string;
    age?: number;
    gender?: string;
    }

    // Pick使用
    type PickA = Pick<A, 'name' | 'age'>;
    // 使用后效果
    // type PickA = {
    // name?: string | undefined;
    // age?: number | undefined;
    // }
    // 实现原理
    type PickATest<T,K extends keyof T> = {
    [Key in K]:T[Key]
    }
  4. Exclude

    // Exclude使用
    type ExcludeA = Exclude<'name' | 'gender' | 'age','age'>;
    // 显示效果
    // type ExcludeA = 'name' | 'gender'

    // 实现原理
    type ExcludeATest<T,K> = T extends K ? never : T
  5. Omit

    interface A {
    name?: string;
    age?: number;
    gender?: string;
    }

    // Omit 使用
    type OmitA = Omit<A, 'a'>;
    // 显示效果
    // type OmitA = {
    // age?: number | undefined;
    // gender?: string | undefined;
    // }
    // 实现原理
    type OmitATest<T,K> = Pick<T,Exclude<keyof T,K>>
  6. Record

    // Record
    type Key = 'name' | 'age' | 'gender'
    type Value = 'x' | 'y' | 'z'
    //Record 使用
    type Test = Record<Key, Value>

    // 实现原理
    type Test1 = keyof any // 等价于 string | number | symbol
    type RecordTest<T extends Test1,K> = {
    [P in T]: K
    }


    const test: RecordTest<Key,Value> = {
    name: 'x',
    age: 'y',
    gender: 'z'
    }
  7. ReturnType

    // ReturnType
    let fn = ()=> [1,2,3]

    // ReturnType 使用
    type ReturnTest = ReturnType<typeof fn> //number[]

    // 原理实现

    type ReturnTypeTest<T> = T extends (...agrs:any[])=>infer R? R :never

    type ReturnTest2 = ReturnTypeTest<typeof fn> //number[]


10 - infer 关键字

// infer 关键字
// infer 声明只能出现在extends子语句中
// infer 后面跟上一个变量名

// 获取promise返回的参数
interface User {
name: string;
age: number;
}

type PromiseType = Promise<User>
type GetPromiseType<T> = T extends Promise<infer X> ? X : T;
type T = GetPromiseType<PromiseType>

//嵌套
type PromiseType1 = Promise<Promise<User>>
type GetPromiseType1<T> = T extends Promise<infer X> ? GetPromiseType1<X> : T;
type T1 = GetPromiseType1<PromiseType1>


//infer协变
type inferTest1<T> = T extends { name: infer U,age:infer U } ? U : never;

type T2 = inferTest1<User> // string | number

//infer逆变

type inferTest2<T> = T extends {a:(name:infer U)=>void,b:(age:infer U)=>void}? U :T

type T3 = inferTest2<{a:(name:string)=>void,b:(age:number)=>void}> // string && number 返回never

11 - infer 提取元素妙用

type Arr = ['a','b','c']

//取到第一项
type First<T extends any[]> = T extends [infer First,...agrs:any[]]? First:never

type FirstTest = First<Arr>

// 取到倒数第一项
type Last<T extends any[]> = T extends [...agrs:any[],infer Last]? Last :never

type LastTest = Last<Arr>

//删除最后一项
type Pop<T extends any[]> = T extends [...infer First,unknown] ? First :never

type PopTest = Pop<Arr>
//删除第一项
type Shift<T extends any[]> = T extends [unknown,...infer Last] ? Last :never

type ShiftTest = Shift<Arr>

//...

12 - TS用法递归

// type Arr = [1,2,3,4] 实现 type newArr = [4,3,2,1]

type Arr = [1,2,3,4]

type ReverseArr<T extends any[]> = T extends [infer First,...infer Rest]?[...ReverseArr<Rest>,First]:T

type newArr = ReverseArr<Arr>

13 - 插件编写(localstroageTime 编写 有时间的localstroage)

// 项目结构
// src -
// - enum // 字典类型文件
// - index.ts
// - types // 接口定义文件
// - index.ts
// - index.ts // 入口文件
// enum / index.ts
// 字典 Dictionaries expire过期时间key permanent 永久不过期
export enum Dictionaries {
permanent = 'permanent',
expire = '__expire__'
}
// types / index.ts
import { Dictionaries } from "../enum/index"

export type Key = string;
export type TimeLimit = number | Dictionaries.permanent;
export type Data<T> = {
value: T;
[Dictionaries.expire]: Dictionaries.permanent | number;
};

export interface Result<T> {
message: string;
value: T | null;
}

export interface LocalStorageTimeCls {
get<T>(key: Key): Result<T | null>;
set<T>(key: Key, value: T, TimeLimit: TimeLimit): void;
remove(key:Key): void;
clear(): void;
}

// index.ts 插件
import {Data, Key, LocalStorageTimeCls,Result,TimeLimit} from "./types/index"
import {Dictionaries} from "./enum/index"

class LocalStorageTime implements LocalStorageTimeCls{
get<T>(key:Key):Result<T | null>{
const value = localStorage.getItem(key)
if(value){
const obj:Data<T> = JSON.parse(value)
const date = new Date().getTime()
if(typeof obj.__expire__ === "number" && obj.__expire__ < date){
this.remove(key)
return {
message:"缓存已过期",
value:null
}
}else{
return {
message:"获取成功",
value:obj.value
}
}
}else{
return {
message:`你的${key}值无效!`,
value:null
}
}
}

set(key:Key,value:any,TimeLimit:TimeLimit = Dictionaries.permanent){
const data = {
key,
value,
[Dictionaries.expire]:TimeLimit
}
localStorage.setItem(key,JSON.stringify(data));
}

remove(key:Key){
localStorage.removeItem(key)
}
clear(){
localStorage.clear()
}
}

export default new LocalStorageTime()