这是我参与[第五届青训营]伴学笔记创作活动的第四天
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定义了a,b但是对象里面缺少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());
方法装饰器
返回三个参数
-
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
-
成员的名字。
-
成员的属性描述符。
[ {}, '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();
属性装饰器
返回两个参数
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 属性的名字。
[ {}, 'name', undefined ]
const met:PropertyDecorator = (...args) => {
console.log(args);
}
class A {
@met
name:string
constructor() {
}
}
const a = new A();
参数装饰器
返回三个参数
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 参数在函数参数列表中的索引。
[ {}, '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"
})
]
}
目录结构
发布订阅模式
概述
什么是发布订阅模式,其实小伙伴已经用到了发布订阅模式例如addEventListener,Vue evnetBus
都属于发布订阅模式
简单来说就是 你要和 大傻 二傻 三傻打球,大傻带球,二傻带水,三傻带球衣。全都准备完成后开始打球。
思维导图
首先 需要定义三个角色 发布者 订阅者 调度者
具体代码
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>