Day4 第五届青训营打卡

97 阅读28分钟

这是我参与[第五届青训营]伴学笔记创作活动的第四天

Day4 TypeScript入门

  • 为什么学习TypeScript?
  • 基础类型
  • 高级类型

为什么学习TypeScript?

TypeScript是一门由微软推出的开源的、跨平台的编程语言。它是JavaScript的超集,扩展了 JavaScript 的语法,最终会被编译为JavaScript代码。

TypeScript的主要特性:

  • 超集 :TypeScript 是 JavaScript 的超集;
  • 类型系统:TypeScript在JavaScript的基础上,包装了类型机制,使其变身为静态类型语言;
  • 编辑器功能 :增强了编辑器和IDE功能,包括代码补全、接口提示、跳转到定义、重构等;
  • 错误提示 :可以在编译阶段就发现大部分错误,帮助调试程序。

TypeScript 主要是为了实现以下两个目标:

  • 为JavaScript提供可选的类型系统;
  • 兼容当前以及未来的JavaScript 的特性。

基础类型

TS是JS的超集,所以JS基础的类型都包含在内

全局安装 npm install typescript -g

运行 tsc 文件名

基础类型:Boolean、Number、String、null、undefined 以及 ES6 的 Symbol 和 ES10 的 BigInt。

1.字符串类型--string

字符串是使用string定义的

let a: string = '123'
//普通声明//也可以使用es6的字符串模板
let str: string = `dddd${a}`

其中 ` 用来定义 ES6 中的模板字符串,${expr} 用来在模板字符串中嵌入表达式。

2.数字类型--number

支持十六进制、十进制、八进制和二进制;

let notANumber: number = NaN;//Nan
let num: number = 123;//普通数字
let infinityNumber: number = Infinity;//无穷大
let decimal: number = 6;//十进制
let hex: number = 0xf00d;//十六进制
let binary: number = 0b1010;//二进制
let octal: number = 0o744;//八进制s

3.布尔类型--Boolean

注意,使用构造函数 Boolean 创造的对象不是布尔值:

let createdBoolean: boolean = new Boolean(1)
//这样会报错 应为事实上 new Boolean() 返回的是一个 Boolean 对象 //事实上 new Boolean() 返回的是一个 Boolean 对象 需要改成
let createdBoolean: Boolean = new Boolean(1)
​
let booleand: boolean = true //可以直接使用布尔值
let booleand2: boolean = Boolean(1) //也可以通过函数返回布尔值

4.空值类型--void

JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数

function voidFn(): void {
    console.log('test void')
}

void 类型的用法,主要是用在我们不希望调用者关心函数返回值的情况下,比如通常的异步回调函数

void也可以定义undefined 和 null类型

let u: void = undefined
let n: void = null;

5.Null和undefined类型

let u: undefined = undefined;//定义undefined
let n: null = null;//定义null

void 和 undefined 和 null 最大的区别,void不能赋值给别人

与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 string 类型的变量:

//这样写会报错 void类型不可以分给其他类型
let test: void = undefined
let num2: string = "1"
num2 = test
​
//这样是没问题的
let test: null = null
let num2: string = "1"
num2 = test
​
//或者这样的
let test: undefined = undefined
let num2: string = "1"
num2 = test

6. Any 类型 和 unknown 顶级类型

nodejs 环境执行ts
npm i @types/node --save-dev (node环境支持的依赖必装)
npm i ts-node --g

1.没有强制限定哪种类型,随时切换类型都可以 我们可以对 any 进行任何操作,不需要检查类型

let anys:any = 123
anys = '123'
anys = true

2.声明变量的时候没有指定任意类型默认为any

let anys;
anys = '123'
anys = true

3.弊端如果使用any 就失去了TS类型检测的作用

4.TypeScript 3.0中引入的 unknown 类型也被认为是 top type ,但它更安全。与 any 一样,所有类型都可以分配给unknown

unknow unknow类型比any更加严格 当你要使用any 的时候可以尝试使用unknow

//unknown 可以定义任何类型的值
let value: unknown;
 
value = true;             // OK
value = 42;               // OK
value = "Hello World";    // OK
value = [];               // OK
value = {};               // OK
value = null;             // OK
value = undefined;        // OK
value = Symbol("type");   // OK
 
//这样写会报错unknow类型不能作为子类型只能作为父类型 any可以作为父类型和子类型
//unknown类型不能赋值给其他类型
let names:unknown = '123'
let names2:string = names
 
//这样就没问题 any类型是可以的
let names:any = '123'
let names2:string = names   
 
//unknown可赋值对象只有unknown 和 any
let bbb:unknown = '123'
let aaa:any= '456'
 
aaa = bbb

区别2

//如果是any类型在对象没有这个属性的时候还在获取是不会报错的
let obj:any = {b:1}
obj.a
 
//如果是unknown 是不能调用没有定义的属性和方法
let obj:unknown = {b:1,ccc:():number=>213}
obj.b
obj.ccc()

7.Object

Object类型是所有Object类的实例的类型。 由以下两个接口来定义:

  • Object 接口定义了 Object.prototype 原型对象上的属性;
  • ObjectConstructor 接口定义了 Object 类的属性, 如上面提到的 Object.create()。

这个类型是跟原型链有关的原型链顶层就是Object,所以值类型和引用类型最终都指向Object,所以他包含所有类型

8.object

object 代表所有非值类型的类型,例如 数组 对象 函数等,常用于泛型约束

let o:object = {}//正确
let o1:object = []//正确
let o2:object = ()=>123 //正确
let b:object = '123' //错误
let c:object = 123 //错误

9.{}

看起来很别扭的一个东西 你可以把他理解成new Object 就和我们的第一个Object基本一样 包含所有类型

tips 字面量模式是不能修改值的

let a1: {} = {name:1} //正确
let a2: {} =  () => 123//正确
let a3: {} = 123//正确
a.age = 2 //错误 字面量不能修改值

接口和对象类型

自定义对象的类型

在typescript中,我们定义对象的方式要用关键字interface(接口),我的理解是使用interface来定义一种约束,让数据的结构满足约束的格式。定义方式如下:

//使用接口约束的时候不能多一个属性也不能少一个属性
//必须与接口保持一致
interface Person {
    b:string,
    a:string
}
//这样写是会报错的 因为我们在person定义了ab但是对象里面缺少b属性
const person:Person  = {
    a:"213"
}

重名interface,内部属性会自动合并

//重名interface  可以合并
interface A{
  name:string
}
interface A{
  age:number
}
var x:A={name:'xx',age:20}
//继承
interface A{
    name:string
}
interface B extends A{
    age:number
}
let obj:B = {
    age:18,
    name:"string"
}

可选属性 使用?操作符

//可选属性的含义是该属性可以不存在
//所以说这样写也是没问题的
interface Person {
    b?:string,
    a:string
}
//b是可选属性,写不写都可以
const person:Person  = {
    a:"213"
}

任意属性 [propName: string]

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

//在这个例子当中我们看到接口中并没有定义C但是并没有报错
//应为我们定义了[propName: string]: any;
//允许添加新的任意属性
interface Person {
    b?:string,
    a:string,
    [propName: string]: any;
}
 
const person:Person  = {
    a:"213",
    c:"123"
}

只读属性 readonly

readonly 只读属性是不允许被赋值的只能读取

interface Person {
    b?: string,
    readonly a: string,
    [propName: string]: any;
}
 
const person: Person = {
    a: "213",
    c: "123"
}
//这样写是会报错的
//应为a是只读的不允许重新赋值
person.a = 123

添加函数

interface Person {
    b?: string,
    readonly a: string,
    [propName: string]: any;
    cb():void
}
 
const person: Person = {
    a: "213",
    c: "123",
    cb:()=>{
        console.log(123)
    }
}

数组类型

使用[]定义类型

//类型加中括号
let arr:number[] = [123]
//这样会报错定义了数字类型出现字符串是不允许的
let arr:number[] = [1,2,3,'1']
//操作方法添加非元素定义类型也是不允许的
let arr:number[] = [1,2,3,]
arr.unshift('1')
 
 
var arr: number[] = [1, 2, 3]; //数字类型的数组
var arr2: string[] = ["1", "2"]; //字符串类型的数组
var arr3: any[] = [1, "2", true,{a:1}]; //任意类型的数组

数组泛型

使用规则 Array<类型>来定义数组 类型指的就是基于内部元素的类型

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

定义多维数组

//使用字面量的方式来定义二维数组
let data:number[][] = [[1,2], [3,4]];
//使用泛型的方式定义多维数组
let data:Array<Array<number|string>> = [[1,2,'hello'], [3,4,'world']];

类数组

function Arr(...arguments:any): void {
    console.log(arguments)
    //错误的arguments 是类数组不能这样定义
    let arr:number[] = arguments
}
Arr(111, 222, 333)
 
 
 
function Arr(...arguments:any): void {
    console.log(arguments) 
    //ts内置对象IArguments 定义
    let arr:IArguments = arguments
}
Arr(111, 222, 333)
 
//其中 IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}

函数类型

//注意,参数不能多传,也不能少传 必须按照约定的类型来
const fn = (name: string, age:number): string => {
    return name + age;
}
fn('张三',18)

函数的可选参数?

//通过?表示该参数为可选参数 其中参数默认值是undefined
const fn = (name: string, age?:number): string => {
    return name + age;
}
fn('张三')//'张三undefined'

函数参数的默认值

//默认值直接在定义的函数参数中进行书写即可
const fn = (name: string = "我是默认值"): string => {
    return name;
}
fn()//"我是默认值"

接口定义函数

//定义参数 num1 和 num2  :后面定义返回值的类型
interface Add {
    (num1:  number, num2: number): number
}
 
const fn: Add = (num1: number, num2: number): number => {
    return num1 + num2
}
fn(5, 5)
 
 //重点理解这个 自定义对象
interface User{
    name: string;
    age: number;
}
function getUserInfo(user: User): User {
  return user
}

定义剩余参数

const fn = (array:number[],...items:any[]):any[] => {
       console.log(array,items)
       return items
}
 
let a:number[] = [1,2,3]
 
fn(a,'4','5','6')

函数重载

重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。

如果参数类型不同,则参数类型应设置为 any

参数数量不同你可以将不同的参数设置为可选。

function fn(params: number): void
 
function fn(params: string, params2: number): void
 
function fn(params: any, params2?: any): void {
 
    console.log(params)
 
    console.log(params2)
 
}
 
fn(123)//函数1
 
fn('123',456)//函数2

泛型

函数泛型

我写了两个函数一个是数字类型的函数,另一个是字符串类型的函数,其实就是类型不同,

实现的功能是一样的,这时候我们就可以使用泛型来优化

function num (a:number,b:number) : Array<number> {
    return [a ,b];
}
num(1,2)
function str (a:string,b:string) : Array<string> {
    return [a ,b];
}
str('独孤','求败')

泛型优化

语法为函数名字后面跟一个<参数名> 参数名可以随便写 例如我这儿写了T

当我们使用这个函数的时候把参数的类型传进去就可以了 (也就是动态类型)

function Add<T>(a: T, b: T): Array<T>  {
    return [a,b]
}
Add<number>(1,2)
Add<string>('1','2')

我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。

function Sub<T,U>(a:T,b:U):Array<T|U> {
    const params:Array<T|U> = [a,b]
    return params
}
Sub<Boolean,number>(false,1)

定义泛型接口

声明接口的时候 在名字后面加一个<参数>

使用的时候传递类型

interface MyInter<T> {
   (arg: T): T
}
 
function fn<T>(arg: T): T {
   return arg
}
 
let result: MyInter<number> = fn
 
result(123)

对象字面量泛型

let foo: { <T>(arg: T): T }
 
foo = function <T>(arg:T):T {
   return arg
}
 
foo(123)

泛型约束

我们期望在一个泛型的变量上面,获取其length参数,但是,有的数据类型是没有length属性的

function getLegnth<T>(arg:T) {
  return arg.length//报错 arg可能并没有length属性
}

这时候我们就可以使用泛型约束

于是,我们就得对使用的泛型进行约束,我们约束其为具有length属性的类型,这里我们会用到interface,代码如下

interface Len {
   length:number
}
 //使用extends进行长度属性的约束 即T必须要有length属性
function getLegnth<T extends Len>(arg:T) {
  return arg.length
}
 
getLength<string>('123')

使用keyof 约束对象

其中使用了TS泛型和泛型约束。

首先定义了T类型并使用extends关键字继承object类型的子类型,

然后使用keyof操作符获取T类型的所有键,它的返回 类型是联合 类型

最后利用extends关键字约束 K类型必须为keyof T联合类型的子类型

function prop<T, K extends keyof T>(obj: T, key: K) {
   return obj[key]
}
let o = { a: 1, b: 2, c: 3 }
prop(o, 'a') 
prop(o, 'd') //此时就会报错发现找不到

泛型类

声明方法跟函数类似名称后面定义<类型>

使用的时候确定类型new Sub()

class Sub<T>{
   attr: T[] = [];
   add (a:T):T[] {
      return [a]
   }
}
 
let s = new Sub<number>()
s.attr = [1,2,3]
s.add(123)
 
let str = new Sub<string>()
str.attr = ['1','2','3']
str.add('123')

生成tsconfig.json 文件

这个文件是通过tsc --init命令生成的

配置详解

"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文件,编译成js文件

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,让我们看一个小例子

命名空间中通过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

嵌套命名空间

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

抽离命名空间

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
    }
}
 //用X表示命名空间的新内容
import X = A.B.C
 
console.log(X);

合并命名空间

重名的命名空间内容会合并

三斜线指令

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

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

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

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

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

a.ts

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

b.ts

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

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

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

声明文件引入 主要引入其他js库

例如,把 /// 引入到声明文件,表明这个文件使用了 @types/node/index.d.ts里面声明的名字

并且,这个包需要在编译阶段与声明文件一起被包含进来。

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

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

注意事项:

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

声明文件 declare

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

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

Mixins混入

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

1.对象混入

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

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

interface Name {
    name: string
}
interface Age {
    age: number
}
interface Sex {
    sex: number
}
 
let people1: Name = { name: "小满" }
let people2: Age = { age: 20 }
let people3: Sex = { sex: 1 }
 //people是一个新类型 交叉Name&Age&Sex
const people = Object.assign(people1,people2,people3)

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 => {
        Object.getOwnPropertyNames(item.prototype).forEach(name => {
            curCls.prototype[name] = item.prototype[name]
        })
    })
}

Decorator

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

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

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

"experimentalDecorators":true

装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,访问符,属性或参数上。

首先定义一个类

@watcher //类修饰符
class A {
    constructor() {
 
    }
}

定义一个类装饰器函数 他会把A的构造函数传入你的watcher函数当做第一个参数

const watcher: ClassDecorator = (target: Function) => {
    target.prototype.getParams = <T>(params: T):T => {
        return params
    }
}
//测试
const a = new A();
console.log((a as any).getParams('123'));

装饰器工厂

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

const watcher = (name: string): ClassDecorator => {
    return (target: Function) => {
        target.prototype.getParams = <T>(params: T): T => {
            return params
        }
        target.prototype.getOptions = (): string => {
            return name
        }
    }
}
 
@watcher('name')
class A {
    constructor() {
 
    }
}
 
const a = new A();
console.log((a as any).getParams('123'));

装饰器组合

就是可以使用多个装饰器

const watcher = (name: string): ClassDecorator => {
    return (target: Function) => {
        target.prototype.getParams = <T>(params: T): T => {
            return params
        }
        target.prototype.getOptions = (): string => {
            return name
        }
    }
}
const watcher2 = (name: string): ClassDecorator => {
    return (target: Function) => {
        target.prototype.getNames = ():string => {
            return name
        }
    }
}
 
@watcher2('name2')
@watcher('name')
class A {
    constructor() {
 
    }
}

const a = new A();
console.log((a as any).getOptions());
console.log((a as any).getNames());

方法装饰器

返回三个参数

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。

  2. 成员的名字。

  3. 成员的属性描述符

    [
      {},
      'setParasm',
      {
        value: [Function: setParasm],
        writable: true,
        enumerable: false,
        configurable: true
      }
    ]
    
const met:MethodDecorator = (...args) => {
    console.log(args);
}
 
class A {
    constructor() {
 
    }
    @met
    getName ():string {
        return '小满'
    }
}
 
const a = new A();

属性装饰器

返回两个参数

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  2. 属性的名字。

[ {}, 'name', undefined ]

const met:PropertyDecorator = (...args) => {
    console.log(args);
}
 
class A {
    @met
    name:string
    constructor() {
    } 
}
const a = new A();

参数装饰器

返回三个参数

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 参数在函数参数列表中的索引。

[ {}, 'setParasm', 0 ]

const met:ParameterDecorator = (...args) => {
    console.log(args);
}
class A {
    constructor() {
    }
    setParasm (@met name:string = '213') {
    }
}
const a = new A();

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打包对比 rollup打包后的空间更小配置json文件

npm init -y 初始化package.json文件

直接将如下代码复制到该json文件中

{
  "name": "rollupTs",
  "version": "1.0.0",
  "description": "",
    //接口
  "main": "index.js",
    //npm run 编译命令
  "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.config.js

console.log(process.env);
//支持typescript
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插件
        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. */
  }
}

webpack构建TS项目

安装依赖

安装webpack   npm install webpack -D

webpack4以上需要 npm install  webpack-cli -D

编译TS  npm install ts-loader -D

TS环境 npm install typescript -D

热更新服务 npm install  webpack-dev-server -D

HTML模板 npm install html-webpack-plugin -D

配置文件ts.config.json

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

目录结构

img

发布订阅模式

概述

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

都属于发布订阅模式

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

思维导图

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

img

具体代码

on订阅/监听

emit 发布/注册

once 只执行一次

off解除绑定

interface EventFace {
    on: (name: string, callback: 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 {
    list: List
    constructor() {
        this.list = {}
    }
    //注册事件
    on(name: string, callback: Function) {
        const callbackList: Array<Function> = this.list[name] || [];
        callbackList.push(callback)
        this.list[name] = callbackList
    }
    //监听事件
    emit(name: string, ...args: Array<any>) {
        let evnetName = this.list[name]
        if (evnetName) {
            evnetName.forEach(fn => {
                fn.apply(this, args)
            })
        } else {
            console.error('该事件未监听');
        }
    }
    //名称和删除的函数
    off(name: string, fn: Function) {
        let evnetName = this.list[name]
        if (evnetName && fn) {
            //删除事件
            let index = evnetName.findIndex(fns => fns === fn)
            evnetName.splice(index, 1)
        } else {
            console.error('该事件未监听');
        }
    }
    //只执行一次
    once(name: string, fn: Function) {
        let decor = (...args: Array<any>) => {
            fn.apply(this, args)
            this.off(name, decor)
        }
        this.on(name, decor)
    }
}
//
const o = new Dispatch()
 
 //参数一事件名称
o.on('abc', (...arg: Array<any>) => {
    console.log(arg, 1);
})
 
o.once('abc', (...arg: Array<any>) => {
    console.log(arg, 'once');
})
// let fn = (...arg: Array<any>) => {
//     console.log(arg, 2);
// }
// o.on('abc', fn)
// o.on('ddd', (aaaa: string) => {
//     console.log(aaaa);
// })
//o.off('abc', fn)
 
o.emit('abc', 1, true, '小满')
 
o.emit('abc', 2, true, '小满')
 
// o.emit('ddd', 'addddddddd')

TS进阶用法

proxy对象代理

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

  • target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 proxy的行为。

    • handler.get() 本次使用的get属性读取操作的捕捉器。
    • handler.set() 本次使用的set属性设置操作的捕捉器。

Reflect

与大多数全局对象不同Reflect并非一个构造函数,所以不能通过new运算符对其进行调用,或者将Reflect对象作为一个函数来调用。

Reflect的所有属性和方法都是静态的(就像Math对象)

  • Reflect.get(target, name, receiver)

Reflect.get方法查找并返回target对象的name属性,如果没有该属性返回undefined

  • Reflect.set(target, name, value, receiver)

Reflect.set方法设置target对象的name属性等于value。

使用proxy进行对象监听

type Person={
    name:string,
    age:number,
    text:string
}
//对象代理 本质使用Reflect的set和get方法进行使用
const proxy = (object:any,key:any)=>{
    return new Proxy(object,{
        get(target,prop,receiver){
            console.log(`get key===>${key}`);
            return Reflect.get(target,prop,receiver)
        },
        set(target,prop,value,receiver){
            console.log(`set key===>${key}`);
            return Reflect.set(target,prop,value,receiver)
        }
    })
}
//使用代理
const logAccess = (object:Person,key:'name'|'age'|'text')=>{
    return proxy(object,key);
}
let obj:Person = {
    name:'wangyu',
    age:18,
    text:'Hello'
}
let man:Person = logAccess(obj,'age')
man.age=200;
console.log(man);

使用泛型+keyof优化

type Person = {
    name: string,
    age: number,
    text: string
}
 
 
const proxy = (object: any, key: any) => {
    return new Proxy(object, {
        get(target, prop, receiver) {
            console.log(`get key======>${key}`);
            return Reflect.get(target, prop, receiver)
        },
 
        set(target, prop, value, receiver) {
            console.log(`set key======>${key}`);
 
            return Reflect.set(target, prop, value, receiver)
        }
    })
}
 
 
const logAccess = <T>(object: T, key: keyof T): T => {
    return proxy(object, key)
}
 
let man: Person = logAccess({
    name: "小满",
    age: 20,
    text: "我的很小"
}, 'age')
 
 
let man2 = logAccess({
    id:1,
    name:"小满2"
}, 'name')
 
man.age = 30
 
console.log(man);

Partial (将泛型T中所有键值都变成可选的)

同理Readonly和其用法一样只不过变成了只读属性

源码

/**
 * 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 是干什么的?

    • keyof 将一个接口对象的全部属性取出来变成联合类型
  • in 是干什么的?

    • in 理解成 循环 就是联合类型的每一项
  • ? 是将该属性变为可选属性

    • ?这个操作就是将每一个属性变成可选项
  • T[P] 是干什么的?

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

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"
 //此时A{age:number,text:string}
type A = Pick<Person,Ex>

Record(做到了同时约束对象的key和value)

源码

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

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

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

3 extends来约束我们的类型

4 T 直接返回类型

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

type Key = string|number|symbol;

type B = Record<Key,Person>;

let obj:B ={
    1:{name:'wangyu',age:18,text:'Hello'},
    2:{name:'wangyu',age:18,text:'Hello'},
}

infer

infer 是TypeScript 新增到的关键字 充当占位符

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

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

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

type A = Infer<(boolean | string)[]>
 
type B = Infer<null>

使用infer 修改

type Infer<T> = T extends Array<infer U> ? U : T

type A = Infer<(string | Symbol)[]>

配合tuple 转换 union 联合类型

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

infer类型提取

1.提取头部元素

类型参数 T 通过extends 约束 只能是数组类型,然后通过infer 声明局部 First 变量做提取,后面的元素可以是任意类型,然后把局部变量返回

type Arr = ['a','b','c']
 //infer First,...any[] 表示推断第一个元素 提取其他元素
type First<T extends any[]> =  T extends [infer First,...any[]] ? First : []
 
type a = First<Arr>

2.提取尾部元素

type Arr = ['a', 'b', 'c']
 
type Last<T extends any[]> = T extends [...any[], infer Last,] ? Last : []
 
type c = Last<Arr>

其实就是反过来就可以了

3.剔除第一个元素 Shift

type Arr = ['a','b','c']
 
type Shift<T extends any[]> =  T extends [unknown,...infer Rest] ? Rest : []
 
type a = Shift<Arr>

4.剔除尾部元素 pop

type Arr = ['a','b','c']
 
type Pop<T extends any[]> =  T extends [...infer Rest,unknown] ? Rest : []
 
type a = Pop<Arr>

infer递归

有这么一个类型

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

希望通过一个 ts 工具变成

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

完整代码

type Arr = [1, 2, 3, 4]
 
type ReveArr<T extends any[]> = T extends [infer First, ...infer rest] ? [...ReveArr<rest>, First] : T
 
type Res = ReveArr<Arr>