1、强类型与弱类型
强类型:不允许改变变量的数据类型,除非进行强制类型转换
弱类型:变量可以被赋予不同的数据类型
2、动态类型与静态类型
静态类型:在编译阶段确定所有变量的类型
动态类型:在执行阶段确定所有变量的类型
3、项目准备
1、初始化工程:npm init -y
2、安装typescript:npm i typescript -g
3、初始化tsc编译器配置文件:tsc --init
4、初始化构建工具:npm i webpack webpack-cli webpack-dev-server -D
5、npm i ts-loader typescript -D
6、npm i html-webpack-plugin -D
7、npm i clean-webpack-plugin -D
8、npm i webpack-merge -D
9、修改package.json
"main": "./src/index.ts",
"scripts": {
"start": "webpak-dev-server --mode=development --config ./build/webpack.config.js"
"build": "webpack --mode=production --config ./build/webpack.config.js"
}
4、基本类型
1、ES6数据类型
Boolean
Number
String
Array
Function
Object
Symbol
undefined
null
2、TypeScript数据类型
Boolean
Number
String
Array
Function
Object
Symbol
undefined
null
void
any
never
元组
枚举
高级类型
5、基本数据类型
// 原始类型
let bool: boolean = true
let num: number = 123
let str: string = 'abc'
// 数组
let arr1: number[] = [1, 2, 3]
let arr2: Array<string | number> = [1, 2, 3, '4']
// 元组:特殊的数组,限定了数组的类型和个数
let tuple: [number, string] = [0, '1']
// 元组越界:可以添加 无法访问
// tuple.push(2)
// tuple[2]
// 函数
let add = (x: number, y: number): number => x + y
let compute: (x: number, y: number) => number
compute = (a, b) => a + b
// 对象
let obj: { x: number, y: string } = { x: 1, y: 'a' }
obj.x = 3
// symbol
let s1: symbol = Symbol()
let s2 = Symbol()
console.log(s1 === s2); // false
// undefined, null
// 只能赋值为本身,是其他类型的子类型
let u: undefined = undefined
let n: null = null
// tsconfig.json "strictNullChecks": false, /* When type checking, take into account `null` and `undefined`. */
// s2 = null
let str2: string | null | undefined = null
// void 没有任何返回值的类型
// js 中为操作符,可以让任何表达式返回undefined
let noReturn = () => { }
// any
let x
x = 1
x = true
// never 永远不会有返回值的类型
let error = () => {
throw new Error('error')
}
let endless = () => {
while (true) {
}
}
6、枚举
枚举:一有名字的常量集合
// 数字枚举 反向映射
// 默认从0开始,依次递增,若指定值,则后面依次递增
enum Role {
Repoter,
Developer = 2,
Maintainer,
Owner,
Guest
}
console.log(Role.Repoter); // 0
console.log(Role);
// 字符串枚举
// 字符串枚举后必须有初始值
// 字符串枚举没有反向映射
enum Message {
N,
Success = '恭喜, 成功了',
Fail = '抱歉,失败了'
}
// 异构枚举
enum Answer {
N,
Y = 'yes'
}
// 枚举成员
// 属性只读
// 分类:常量枚举,编译时出现结果,以常量形式出现在运行时环境
// 分类:需要被计算的枚举成员,一些非常量的表达式,编译阶段不会计算,保留到程序运行阶段,之后的枚举成员必须赋予初始值
enum Char {
// const,常量枚举
// 1 没有初始值
// 2 对已有枚举成员的引用
// 3 常量表达式
a,
b = Char.a,
c = 1 + 3,
// computed
d = Math.random(),
e = '123'.length
}
// 常量枚举
// 编译阶段被移除
// 作用:在需要对象的值,且不需要对象时使用
const enum Month {
Jan,
Feb,
Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar]
// 枚举类型
// 枚举或者枚举成员作为一种类型
// 1、枚举成员没有初始值
// 2、枚举成员都为数字枚举
// 3、枚举成员都为字符串枚举
enum E { a, b }
enum F { a = 0, b = 1 }
enum G { a = 'apple', b = 'banana' }
// 不同枚举类型无法比较
let e: E = 3
let f: F = 3
// e === f
// 不同枚举成员类型无法比较
// 相同枚举成员类型可以比较
let e1: E.a = 1
let e2: E.b
// el === e2
let e3: E.a = 1
e1 === e3
// 字符串枚举
// 取值只能为枚举成员
let g1: G = G.a
let g2: G.b = G.b
7、接口
接口:约束对象、函数、类的结构和类型
7.1、对象接口
// 1 对象接口
// 接口属性个数固定
interface List {
readonly id: number; // 只读属性
name: string;
age?: number // 可选属性
}
interface Result {
data: List[]
}
function render(result: Result) {
result.data.forEach(value => {
console.log(value.id, value.name);
if (value.age) console.log(value.age)
// value.id++
})
}
// 直接传入对象字面量 会对额外字段进行类型检查
// render({
// data: [
// { id: 1, name: 'A', sex: 'male' }
// ]
// })
// 绕过这种类型检查
// 方式1、把对象字面量赋值给变量
let result = {
data: [
{
id: 1,
name: 'A',
sex: 'male' // 鸭式分辨法,传入对象满足接口的必要条件,即可通过类型检查
},
{
id: 2,
name: 'B',
age: 12
}
]
}
render(result)
// 方式2 类型断言
// 断言方式1
render({
data: [
{ id: 1, name: 'A', sex: 'male' },
{ id: 2, name: 'B'}
]
} as Result)
// 断言方式2
render(<Result>{
data: [
{ id: 1, name: 'A', sex: 'male' },
{ id: 2, name: 'b'}
]
})
// 3 字符串索引签名 ?
/**interface List {
id: number;
name: string;
[x: string]: any; // 用任意的字符串索引List,得到任意的List
} */
// 可索引类型接口
// 接口属性个数不确定
// 1 数字索引接口
interface StringArray {
[index: number]: string // 签名:用任意的数字索引StringArray都会得到一个string
}
let chars: StringArray = ['A', 'B']
let chars2: StringArray = {
1: 'a',
2: 'b'
}
console.log('chars2', chars2)
// 2 字符串索引接口
interface Names {
[x: string]: string; // 用任意的字符串索引Names,得到的结果都是string
// y: number // 不能声明number类型成员
[z: number]: string // 索引签名可以混用,数字索引签名的返回值一定要是字符串索引签名返回值的子类型
}
let strs: Names = {
a: 'b',
c: 'd',
1: '2'
}
console.log(strs)
7.2、 函数接口
// 1 使用变量定义函数类型
let add1: (x: number, y: number) => number
// 2 使用接口定义函数类型
interface Add {
(x: number, y: number) :number
}
// 3 使用类型别名定义函数类型
type Add2 = (x: number, y: number) => number
let add2: Add2 = (a, b) => a + b
// 混合接口
interface Lib {
(): void;
version: string;
doSomething(): void
}
function getLib() {
let lib: Lib = (() => { }) as Lib
lib.version = '1.0'
lib.doSomething = () => { }
return lib
}
let lib1 = getLib()
lib1()
lib1.doSomething()
let lib2 = getLib()
8、TS函数
// 函数定义
function add11(x: number, y: number) {
return x + y
}
let add22: (x: number, y: number) => number
type add33 = (x: number, y: number) => number
interface add4 {
(x: number, y: number): number
}
// 函数参数个数和类型必须一一对应
add11(1, 2)
// 可选参数
// 必须位于必选参数后面
function add5(x: number, y?: number) {
return y ? x + y : x
}
add5(1)
// 参数默认值
function add6(x: number, y = 0, z: number, q = 1) {
return x + y + z + q
}
// 必选参数前的默认值参数如果不传值,需要传入undefined
console.log(add6(1, undefined, 3));
// 剩余参数:参数个数不确定
function add7(x: number, ...rest: number[]) {
return x + rest.reduce((total, item) => total + item)
}
console.log(add7(1, 2, 3, 4, 5));
// 函数重载:函数名相同,参数不同,实现不同功能
function add8(...rest: number[]): number
function add8(...rest: string[]): string
function add8(...rest: any[]): any {
if (typeof rest[0] === 'string') {
return rest.join('')
}
if (typeof rest[0] === 'number') {
return rest.reduce((pre, cur) => pre + cur)
}
}
console.log(add8(1, 2, 3), add8('a', 'b', 'c'));
9、TS类
9、1
// 类成员的属性都是实例属性
// 类成员的方法都是原型方法
// TS中,实例的属性必须具有初始值,或在构造函数中被初始化
class Dog {
constructor(name: string, age: number) {
this.name = name
// this.age = age
}
name: string
age?: number // 可选属性
sex: string = 'male'
run() {}
}
console.log(Dog.prototype);
console.log(new Dog('a', 2));
// 类的继承
//super调用的时候,必须继承父类中的所有属性
class Husky extends Dog {
constructor(name: string, age: number, color: string) {
super(name, age)
this.color = color
}
color: string
}
// 类的成员修饰符
// public 默认修饰符,共有成员可以被任意访问
// private 私有成员,只能被当前类访问,不能被实例或子类访问
// protected 只能被当前类或子类访问,实例对象无法访问
// readonly 只读成员,必须有初始值,不能更改
// static 静态成员 只能被当前类和子类访问,不能被实例成员访问
class Dog2 {
// 构造函数添加private修饰符:表示当前类既不能被实例化,也不能被继承
// 构造函数的参数添加修饰符,将参数自动变成实例属性,省略在类中的定义
private constructor(name: string, protected age: number) {
this.name = name
this.age = age
}
public name: string
readonly legs: number = 4
static food: string = 'bones'
private run() {}
}
class Dog3 {
// 构造函数添加protected修饰符:表示当前类不能被实例化,只能被继承
protected constructor(name: string) {
this.name = name
}
name: string
}
9、2抽象类与多态
// 只能被继承,不能被实例化的类
// 抽象类使用abstract关键字声明
abstract class Animal {
// 具体实现方法,方法复用
eat() {
console.log('eat');
}
// 抽象方法
// 实现多态:抽象类中的抽象方法,在子类中有不同的实现,程序运行中根据不同的对象执行不同的操作
abstract sleep(): void
}
// const animal = new Animal()
class Dog8 extends Animal {
constructor(name: string) {
super()
this.name = name
}
name: string
run() { }
sleep() {
console.log('sleep1');
}
}
let dog = new Dog8('s')
dog.eat()
// 实现多态
class Cat extends Animal {
sleep() {
console.log('cat sleep');
}
}
let cat = new Cat()
let animals: Animal[] = [dog, cat]
for (let i = 0; i < animals.length; i++) {
animals[i].sleep()
}
10、this类型
// 类的成员方法返回this,实现链式调用
class WorkFlow {
step1() {
return this
}
step2() {
return this
}
}
new WorkFlow().step1().step2()
// 在继承中实现多态
// this指向可以是父类型,也可以是子类型
class MyFlow extends WorkFlow {
next() {
return this
}
}
new MyFlow().next().step1().next().step2()
11、类接口
// 约束类的成员及类型
// 只能约束类的共有成员
// 不能约束类的构造函数
interface Human {
// new (name: string): void
name: string;
eat(): void
}
class Asian implements Human {
constructor(name: string) {
this.name = name
}
// private name: string
name: string
eat() { }
// 可以声明接口中没有的成员
sleep() {}
}
// 接口继承
interface Man extends Human {
run(): void
}
interface Child {
cry(): void
}
interface Boy extends Man, Child { }
let boy: Boy = {
name: '',
eat() { },
run() { },
cry() {}
}
// 接口继承类,相当于把类的成员抽象出来,只有成员的结构,没有具体的实现
// 接口抽离了类的公共成员、私有成员和受保护成员
class Auto {
state = 1
// private state2 = 0
}
interface AutoInterface extends Auto { }
class C implements AutoInterface {
state = 2
}
// 子类继承了 state 实现了AutoInterface接口
class Bus extends Auto implements AutoInterface {}
12、类与接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sb2nb3Ki-1638793440860)(C:\resourse\前端笔记\78-极客-typescript\images\Snipaste_2021-10-12_00-33-10.png)]
1、接口之间相互继承,实现接口复用
2、类之间相互继承,实现方法和属性的复用
3、接口可以通过类实现,接口只能约束类的共有成员
4、接口可以抽离类的成员,包括共有成员、私有成员和受保护成员
13、泛型
泛型:不预先确定数据类型,具体的类型在使用中的时候才能确定
1、函数和类可以轻松的支持多种类型,增强程序的扩展性
2、不必写多条函数重载,冗长的联合类型声明,增强代码可读性
3、灵活控制类型之间的约束
// 泛型函数
function log<T>(value: T): T {
console.log(value);
return value
}
// 1、指定泛型类型
log<string[]>(['a', 'b'])
// 2 类型推断
log([1, 2])
// 泛型函数类型
type Log = <T>(value: T) => T
let myLog: Log = log
// 泛型接口
// 实现泛型接口必须指定泛型类型
interface Log2<T> {
(value: T): T
}
let myLog2: Log2<String> = log
// 指定泛型接口默认类型
interface Log3<T = number> {
(value: T): T
}
let myLog3: Log3 = log
myLog3(2)
14、泛型类
// 无法约束类的静态成员
class Log4<T> {
run(value: T) {
console.log(value);
return value
}
// static uname: T
}
let log11 = new Log4<number>()
log11.run(1)
// 不指定类型参数,泛型可以是任意类型
let log22 = new Log4()
log22.run({ id: 1 })
//泛型约束
interface Length {
length: number
}
// 当前泛型收到约束,不管传入什么数据类型,必须包含length属性
function log33<T extends Length>(value: T): T {
console.log(value, value.length);
return value
}
log33([1])
log33('abc')
log33({length: 1})
15、类型检查机制
类型检查机制:TypeScript编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为
辅助开发,提高开发效率
1、类型推断
2、类型兼容性
3、类型保护
15.1类型推断
类型推断:不需要指定变量的类型(函数返回值的类型),TypeScript可以根据某些规则自动地为其推断出一个类型
基础类型推断
最佳通用类型推断
上下文类型推断
// 从右向左推断,根据表达式右侧的值推断表达式左侧变量的类型
let a = 1 // number
let b = [1] // number[]
// 最佳通用类型推断:从多个类型中推断出一个类型时,TS会推断出兼容当前所有类型的通用类型
let d = [1, null]
let c = (x = 1) => x + 1 // number number
// 上下文类型推断:从左到右,通常发生在事件处理中,根据左侧的事件绑定,推断出事件类型和相应的属性
window.onkeydown = (event) => { // KeyboardEvent
// console.log(event.button);
}
// 类型断言 as
interface Foo {
bar: number
}
let foo = {} as Foo
foo.bar = 1
// 类型断言 <>
let foo2 = <Foo>{}
foo.bar2 = 2
15.2、类型兼容性
当一个类型Y可以被赋值给另一个类型X时,我们就可以说类型X兼容类型Y
X兼容Y:X(目标类型) = Y(源类型)
结构之间兼容:成员少的兼容成员多的
函数之间兼容:参数多的兼容参数少的
/**
* X 兼容 Y:X(目标类型) = Y(源类型)
*/
let s: string = 'a'
s = null
// 接口兼容性
// 源类性必须具备目标类型的必要属性,才可以进行赋值
// 成员少的可以兼容成员多的
interface X {
a: any;
b: any;
}
interface Y {
a: any;
b: any;
c: any;
}
let xx: X = { a: 1, b: 2 }
let y: Y = { a: 1, b: 2, c: 3 }
xx = y
// 函数兼容性
// 函数类型为目标函数
// 实参为源函数
type Handler = (a: number, b: number) => void
function hof(handler: Handler) {
return handler
}
// 1 参数个数
// 参数个数固定:目标函数的个数要大于源函数的个数
let handler1 = (a: number) => { }
hof(handler1)
let handler2 = (a: number, b: number, c: number) => { }
// hof(handler2)
// 可选参数和剩余参数
let a1 = (p1: number, p2: number) => { }
let a2 = (p1?: number, p2?: number) => { }
let a3 = (...args: number[]) => { }
// 固定参数兼容可选参数和剩余参数
// a1 = a2
// a1 = a3
// 可选参数不兼容固定参数和剩余参数
// "strictFunctionTypes": false
a2 = a1
a2 = a3
// 剩余参数兼容固定参数和可选参数
a3 = a1
a3 = a2
// 2 参数类型
let handler3 = (a: string) => { }
// hof(handler3)
// 对象参数
// 对象参数中成员多的兼容成员少的
interface Point3D {
x: number;
y: number;
z: number;
}
interface Point2D {
x: number;
y: number;
}
let p3d = (point: Point3D) => {}
let p2d = (point: Point2D) => { }
// 函数参数双向协变:允许函数把精确的类型赋值给不精确的类型
p3d = p2d
// "strictFunctionTypes": false
p2d = p3d
// 3 返回值类型
// 目标函数的返回值类型必须要与源函数的返回值类型相同 或者为其子类型
// 成员少兼容成员多
let ff = () => ({ name: 'Alice' })
let g = () => ({ name: 'Alice', location: 'Beijing' })
ff = g
// 函数重载
// 重载列表:目标函数
// 目标函数的参数要多于源函数的参数,返回值要符合相应的约束
function overload(a: number, b: number) : number;
function overload(a: string, b: string): string;
// 具体实现:源函数
function overload(a: any, b: any): any { }
// 枚举兼容性
// 数字枚举
enum Fruit { Apple, Banana }
enum Color { Red, Yellow }
// 数字枚举类型和数字类型互相兼容
let fruit: Fruit.Apple = 1
let no: number = Fruit.Apple
// 枚举之间互不兼容
// let color: Color.Red = Fruit.Apple
// 类兼容性
// 类的静态成员和构造函数不参与比较
// 两个类拥有相同的实例成员,则相互兼容
// 类中含有私有成员,则不兼容,此时只有父类和子类之间相互兼容
class Aa {
constructor(p: number, q: number) { }
id: number = 1
// private name: string = ''
}
class Bb {
static s = 1
constructor(p: number) { }
id: number = 2
// private name: string = ''
}
let aa = new Aa(1, 2)
let bb = new Bb(1)
aa == bb
bb == aa
// 父类和字类之间可以相互兼容
class Cc extends Aa { }
let cc = new Cc(1, 2)
aa = cc
cc = aa
// 泛型兼容性
// 泛型接口
// 只有接口成员T被泛型成员使用时才会影响泛型兼容性
interface Empty<T> { value: T }
let obj1: Empty<number> = { value: 1 }
let obj2: Empty<string> = { value: 'a' }
// obj1 = obj2
// obj2 = obj1
// 泛型函数
// 如果两个泛型函数的定义相同,但是没有指定类型参数,则互相兼容
let log1 = <T>(x: T): T => {
console.log('x');
return x
}
let log2 = <U>(y: U): U => {
console.log('y');
return y
}
log1 = log2
log2 = log1
15.3 类型保护
TypeScript能够在特定的区块中保证变量属于某种确定的类型
可以在此区块中的引用此类型的属性,或者调用此类型的方法
enum Type { Java, JavaScript }
class Java {
helloJava() {
console.log('Hello Java');
}
java: any
}
class JavaScript {
helloJavaScript() {
console.log('Hello JavaScript');
}
javascript: any
}
// 类型保护函数
// 返回值:类型谓词
function isJava(lang: Java | JavaScript): lang is Java {
return (lang as Java).helloJava !== undefined
}
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Java ? new Java() : new JavaScript()
// if ((lang as Java).helloJava) {
// (lang as Java).helloJava()
// } else {
// (lang as JavaScript).helloJavaScript()
// }
// instanceof 判断当前对象所属类类型
if (lang instanceof Java) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
// in 判断某个属性是否属于某个对象
if ('java' in lang) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
// typeof
if (typeof x === 'string') {
x.length
} else {
x.toFixed()
}
// 通过创建类型保护函数判断对象的类型
if (isJava(lang)) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
}
16、高级类型
16.1、交叉类型&联合类型
交叉类型:将多个类型合并为一个类型,新的类型具有所有类型的特性
联合类型:声明的类型不确定,可以是多个类型中的一个
// 交叉类型:取所有类型的并集
interface DogInterface {
run(): void
}
interface CatInterface {
jump(): void
}
let pet: DogInterface & CatInterface = {
run() {},
jump() {}
}
let aaa: number | string = 'a'
// 字面量类型:限定变量的类型和取值
let bbb: 'a' | 'b' | 'c'
let ccc: 1 | 2 | 3
// 对象联合类型
class Dog1 implements DogInterface {
run() { }
eat() {}
}
class Cat1 implements CatInterface {
jump() { }
eat() {}
}
enum Master { Boy, Girl }
function getPet(master: Master) {
// 取所有类型的交集
let pet = master === Master.Boy ? new Dog1() : new Cat1()
pet.eat()
// pet.jump()
return pet
}
// 可区分的联合类型:结合了联合类型和字面量类型的一种类型保护方法
// 一个类型如果是多个类型的联合类型,并且每个类型之间有一个公共的属性,那么可以根据这个公共属性,创建不同的类型保护区块
interface Square {
kind: 'square';
size: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
interface Circle {
kind: 'circle',
r: number
}
// ts约束可区分的联合类型 ?
type Shape = Square | Rectangle | Circle;
// 1 为函数指定明确的返回值类型:会判断所有的分支是否包含所有的情况
// function area(s: Shape): number {
function area(s: Shape): number {
switch (s.kind) {
case 'square':
return s.size * s.size;
case 'rectangle':
return s.width * s.height;
// default分支
case 'circle':
return Math.PI * s.r * 2;
default:
// 2、检查参数s是否为never类型,如果不是never类型,说明前面分支有遗漏
return ((e: never) => {throw new Error(e)})(s)
}
}
console.log(area({kind: 'circle', r: 2}));
16.2、索引类型
let obj11 = {
a: 1,
b: 2,
c: 3
}
// 获取对象中属性值组成的数组
function getValues(obj: any, keys: string[]) {
return keys.map(key => obj[key])
}
console.log(getValues(obj11, ['a', 'b']));
console.log(getValues(obj11, ['e', 'f']));
// 进行类型约束(获取对象中不存在的属性):索引类型
// 索引类型的查询操作符:keyof T 表示T的所有公共属性的字面量的联合类型
interface objoo {
a: number;
b: string;
}
let key: keyof objoo
// 索引访问操作符 T[K] 代表对象T的属性K所代表的类型
let value: objoo['a']
// T extends U 泛型约束:泛型变量通过继承某个类型或某些属性
// 约束参数keys中的属性一定是对象obj中的属性
// 索引类型可以实现对对象属性的查询和访问,配合泛型约束可以建立对象、对象属性、属性值之间的约束关系
function getValues2<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
return keys.map(key => obj[key])
}
console.log(getValues2(obj11, ['a', 'b']));
// console.log(getValues2(obj11, ['a', 'f']));
16.3、映射类型
从一个旧类型中生成一个新类型
interface Obj9 {
a: string;
b: number;
c: boolean;
}
// TS内置接口(映射类型)
// 同态:只会作用于Obj9的属性,不会引入新的属性
// 将接口中所有属性变为只读
type ReadonlyObj = Readonly<Obj9>
// 将接口所有属性变为可选
type PartialObj = Partial<Obj9>
// 抽取接口子集
type PickObj = Pick<Obj9, 'a' | 'b'>
// 非同态类型:增加新的类型
// Record https://blog.csdn.net/weixin_38080573/article/details/92838045 ?
// [k in T] https://juejin.cn/post/6844904066481389575#refetch ?
// 将一个类型的所有属性值都映射到另一个类型上并创造一个新的类型
type RecordObj = Record<'x' | 'y', Obj9>
=>
const o: RecordObj = {
x: {
a: 'string';
b: 1;
c: true;
},
y: {
a: 'stirng',
b: 2,
c: false
}
}
16.4、条件类型
由条件表达式所决定的类型
// T extends U ? X : Y
// 类型T是否可以赋值给类型U
// 是:类型X
// 否:类型Y
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"obj";
type T1 = TypeName<string>
type T2 = TypeName<string[]>
// 分布式条件类型
// T为条件类型,则结果是多个条件类型的联合类型
// (A | B) extends U ? X : Y
// (A extends U ? X : Y) | (B extends U ? X : Y)
type T3 = TypeName<string | string[]>
// 分布式条件类型 实现类型过滤
// 过滤类型T中可以赋值给类型U的类型
type Diff<T, U> = T extends U ? never : T
type T4 = Diff<'a' | 'b' | 'c', "a" | "e">
// Diff<'a', "a" | "e"> | Diff<'b', 'a' | 'e'> | Diff<'c', 'a' | 'e'>
// never | 'b' | 'c'
// 'b' | 'c'
// 过滤undefined null
type NotNull<T> = Diff<T, undefined | null>
type T5 = NotNull<string | number | undefined | null>
// TS内置条件类型
// Exclude<T, U> 过滤类型T(联合类型)中可以赋值给类型U的类型
// NoNullable<T> 过滤undefined和null类型
// Extract<T, U> 提取类型T可以赋值给类型U的类型
type T6 = Extract<'a' | 'b' | 'c', 'a' | 'e'>
//ReturnType<T> 获取函数返回值类型,参数为函数
// infer关键字?
type T7 = ReturnType<() => string>
17、ES6模块化
17.1、导出
// 单独导出
export let a = 1
// 批量导出
let b = 2
let c = 3
export { b, c }
// 导出接口
export interface P {
x: number;
y: number;
}
// 导出函数
export function f() {}
// 导出时起别名
function g() {}
export { g as G }
// 默认导出 无需函数名
export default function() {
console.log("I'm default")
}
// 引入外部模块 重新导出
export { str as hello } from './b.ts'
// b.ts
export const str = 'hello'
17.2、导入
// 批量导入
import { a, b, c } from './a';
// 导入接口
import { P } from './a'
// 导入时起别名
import { f as F } from './a'
// 导入模块中的所有成员
import * as All from './a'
// 默认导入
import myFunction from './a
18、CommonJS模块化
18.1、导出
// 整体导出
let a = {
x: 1,
y: 2
}
module.exports = a
// 导出多个变量
// exports === module.exports
// 覆盖后面导出
module.exports = {}
exports.c = 3
exports.d = 4
18.2、导入
let c1 = require('./a.node')
let c2 = require('./b.node')
18.3 、tsconfig.json
// 编译的版本,默认es5,tsc中默认为es3
"target": "es5"
// 编译生成的模块,默认commonjs
"module": "commonjs"
18.4、ES6与CommonJS模块化
ES6中允许有默认导出和按需导出两个*对象
CommonJS中之允许有一个*对象
CommonJS导入ES6模块,如果ES6模块导出两个*对象,默认导出会被挂载到CommonJS*对象的default属性上
CommonJS和ES6模块化冲突解决方案:
1、两个模块化方案不混用
2、TS兼容性语法
// ES6模块有一个*导出,且有可能被CommonJS模块引用
// TS兼容性语法
// 会被编译成module.exports,相当于CommonJS中的*导出
// 无法再进行其他导出
export = {}
// 兼容性导入
import c = require('./es6/d')
// tsconfig.json "esModuleInterop": "false",关闭后无法使用ES6语法导入
// 也可以使用ES6导入语法 import c from './es6/d'
19、命名空间
本质上为闭包,用于隔离作用域
// 命名空间内的变量只能局部可见
// 使用export关键字可以使变量全局可见
// a.ts
namespace Shape {
const pi = Math.PI
export funciton circle(r: number) {
return pi * r ** 2
}
}
// b.ts
// 不同文件之间的同名命名空间共享一个空间
// 三斜线引用a.ts 相对路径
/// <reference path="a.ts" />
namespace Shape {
export function square(x: number) {
return x * x
}
}
Shape.circle(1)
Shape.square(1)
// 命名空间成员起别名
import circle = Shape.circle
console.log(circle(2))
// 命名空间和模块不要混用,不要在模块中使用命名空间,在全局环境中使用命名空间
20、声明合并(TS)
编译器把程序多个地方拥有相同名称的声明合并成一个声明
20.1、接口声明合并
// 非函数成员必须保证唯一性,如果不唯一则必须保证类型相同
// 函数成员重复:每一个函数都会被声明为函数重载
// 接口声明合并函数重载顺序:
// 接口内部按书写顺序排序
// 接口之间后边的接口排在前面
// 如果函数的参数为字符串字面量,则会被提升到整个函数声明的最顶端
// 重载顺序:接口书写顺序 => 函数参数为字符串字面量 => 接口内部书写顺序
interface A {
x: number;
// y: string;
foo(bar: number): number; // 5
foo(bar: 'A'): number; // 2
}
interface A {
y: number;
foo(bar: string): string; // 3
foo(bar: number[]): number[]; // 4
foo(bar: 'b'): number; // 1
}
let a: A = {
x: 1,
y: 2,
foo(bar: any): any {
return bar
}
}
20.2、命名空间生命合并
不同文件的同名命名空间共享一个空间
命名空间导出的成员不可以重复定义
20.3、命名空间和函数声明合并
相当于函数的属性
命名空间必须放到函数后面
function Lib() {}
namespace Lib {
export let version = '1.0'
}
console.log(Lib.version)
20.4、命名空间和类声明合并
相当于类的静态属性
命名空间必须放到类后面
class C {}
namespace C {
export let state = 1
}
console.log(C.state)
20.5、命名空间和枚举生命合并
相当于枚举类型的方法
enum Color {
Red,
Yellow,
Blue
}
namespace Color {
export function mix() {}
}
console.log(Color)
21、TS引入外部类库,编写声明文件
引入非TS引入的外部类库,必须为类库编写声明文件
// 安装类型声明包
npm i @types/包名
查询引入非TS包是否有类型声明文件
https://www.typescriptlang.org/dt/search/
编写类型声明文件指引
https://definitelytyped.org/guides/contributing.html
1、全局类库
// global-lib.js
function globalLib(options) {
console.log(options)
}
globalLib.version = '1.0.0'
globalLib.doSomething = function() {
console.log('globalLib do something')
}
// 全局类库声明文件
// global-lib.d.ts
declare function globalLib(options: globalLib.Options): void;
declare namespace globalLib {
const version: string;
function doSomething(): void;
interface Options {
[key: string]: any
}
}
// index.html中导入为全局类库
// <script src=".../global-lib.js"></script>
globalLib.doSomething()
2、模块类库
// module-lib.js
const version = '1.0.0';
function doSomething() {
console.log('moduleLib do something')
}
function moduleLib(options) {
console.log(options);
}
moduleLib.version = version;
moduleLib.doSomething = doSomething;
module.exports = moduleLib;
// 模块类库声明文件
// module-lib.d.ts
declare function moduleLib(options: Options): void
interface Options {
[key: string]: any
}
declare namespace moduleLib {
// export const version: string
const version: string
function doSomething(): void
}
export = moduleLib
// 模块引用
import moduleLib from './module-lib'
moduleLib.doSomething()
3 umd类库
// umd-lib.js
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
root.umdLib = factory();
}
}(this, function() {
return {
version: '1.0.0',
doSomething() {
console.log('umdLib do something');
}
}
}));
// umd类库声明文件
// umd-lib.d.ts
declare namespace umdLib {
const version: string
function doSomething(): void
}
export as namespace umdLib
export = umdLib
// 模块引用
import umdLib from './umd-lib'
umdLib.doSomething()
// 全局引用
//index.html中导入
// <script src=".../umd-lib.js"></script>
// tsconfig.json "allowUmdGlobalAccess": true 关闭TS全局引用umd类库报错提示
umdLib.doSomething()
给类库添加自定义方法
// 模块化插件
import m from 'moment'
declare module 'moment' {
export function myFunction(): void
}
m.myFunction = () => {}
// 全局插件 给全局变量添加方法
// 对全局命名空间造成污染 ?
declare global {
namespace globalLib {
function doAnything(): void
}
}
globalLib.doAnthing = () => {}
声明文件的依赖:声明文件按模块划分
// 模块依赖
// TS在@types目录下寻找模块,并引入相应声明文件
/// <reference types="sizzle" />
// 路径依赖 相对路径
/// <reference path="Jquery.d.ts" />
22、tsconfig.json配置
22.1、文件相关配置
// 执行tsc命令
// tsconfig.base.json
{
// TS要编译的单个文件列表,无配置,输入tsc则会编译整个项目中的ts文件
"files": [
"src/a.ts"
],
// 编译器要编译的文件或目录
"include": [
// 编译src目录下的所有文件,包括子目录的文件
"src",
// 只会编译src一级目录下的文件
"src/*",
// 只会编译src二级目录下的文件
"src/*/*"
],
// 编译器需要排除的文件或文件夹
// 默认排除node_modules下的所有文件和所有的声明文件
// 排除src/lib文件夹下的文件
"exclude": [
"src/lib"
]
}
// tsconfig.json
{
// 配置文件抽离,通过extends导入
"extends": "./tsconfig.base"
// 覆盖base中的配置
"exclude": [],
// 保存时自动编译,vscode不支持
"compileOnSave": true
}
22.2、编译相关配置
// 执行tsc命令
{
"compileOptions": {
"incremental": true, // 增量编译:在第一次编译后,存储一个编译信息的文件,在二次编译的时候根据生成文件进行增量编译,提高编译速度
"diagnostics": true, // 打印诊断信息
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"target": "es5", // 目标语言的版本
"module": "commonjs", // 生成代码的模块标准
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中
"lib": [], // TS需要引用的库,即声明文件,es5默认 "dom", "es5", "scripthost"
"allowJs": true, // 允许编译 JS 文件 (js、jsx)
"checkJs": true, // 允许在 JS 文件报错,通常与 allowJS 一起使用
"outDir": "./out", // 指定输出的目录
"rootDir": "./", // 指定输入文件目录(用于控制输出目录结构)
"declaration": true, // 生成声明文件
"declarationDir": "./d", // 声明文件的路径
"emitDeclarationOnly": true, // 只生成声明文件
"sourceMap": true, // 生成目标文件的 sourceMap
"inlineSourceMap": true, // 生成目标文件的 inline sourceMap
"declarationMap": true, // 生成声明文件的 sourceMap
"typeRoots": [], // 声明文件目录,默认查找node_modules/@types
"types": [], // 声明文件包,默认加载@types下所有声明文件,如果指定了某一个包,则只加载当前指定包声明文件
"removeComments": true, // 删除注释
"noEmit": true, // 不输出文件
"noEmitOnError": true, // 发生错误时不输出文件
"noEmitHelpers": true, // (与类的继承相关)不生成 helper 函数,需额外安装ts-helpers
"importHelpers": true, // 通过 tslib 引入 helper 函数,文件必须是模块
"downlevelIteration": true, // 降低遍历器的实现(es3/5)
// 类型检查相关
"strict": true, // 开启所有严格的类型检查,开启后,以下选项均默认为true,
"alwaysStrict": true, // 在代码中注入 "use strict"
"noImplicitAny": true, // 不允许隐式的 any 类型(必须有类型注解)
"strictNullChecks": true, // 不允许把 null、undefined 赋值给其他类型变量
"strictFunctionTypes": true, // 不允许函数参数的双向协变 ?
"strictPropertyInitialization": false, // 类的实例属性必须初始化
"strictBindCallApply": false, // 严格的 bind/call/apply 检查
"noImplicitThis": false, // 不允许 this 有隐式的 any 类型 ?
// 函数相关选项,只会对函数提出错误提示,不会阻碍预编译
"noUnusedLocals": true, // 检查只声明,未使用的局部变量
"noUnusedParameters": true, // 检查未使用的函数参数
"noFallthroughCasesInSwitch": true, // 防止 switch 语句贯穿(某一个case中未使用break关键字,导致下边的分支都会执行)
"noImplicitReturns": true, // 每个分支都要有返回值
"esModuleInterop": true, // 允许 export = 导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中访问 UMD 全局变量
"moduleResolution": "node", // 模块解析策略(默认node,classic)
"baseUrl": "./", // 解析非相对模块的基地址,默认为当前路径
"paths": { // 路径映射,相对于 baseUrl
"jquery": ["node_modules/jquery/dist/jquery.slim.min.js"]
},
"rootDirs": ["src", "out"], // 将多个目录放在一个虚拟目录下,用于运行时
"listEmittedFiles": true, // 打印输出的文件
"listFiles": true, // 打印编译的文件(包括引用的声明文件)
}
}
// 编译声明文件
tsc 文件 -d
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mCFxQl0-1638793440861)(C:\resourse\前端笔记\78-极客-typescript\images\TS解析策略classic.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OCnRyWUH-1638793440862)(C:\resourse\前端笔记\78-极客-typescript\images\TS解析策略node.png)]
23、TS3.0新特性(工程引用)
工程引用:可以灵活配置输出目录,可以使工程之间产生依赖关系,把大项目拆分成一个个小的项目,同时可以用增量编译提升编译速度
优点:
1、解决输出目录结构问题
2、解决单个工程构建问题
3、通过增量编译提升速度
1、为每个工程添加tsconfig.json配置文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oumUa8wJ-1638793440863)(C:\resourse\前端笔记\78-极客-typescript\images\工程引用.png)]
2、基础配置
// 输出目录由各自工程指定
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"composite": true, // 工程可以被引用,并且可以进行增量编译
"declaration": true // 必须生成声明文件
}
}
3、工程配置
{
"extends": "../../tsconfig.json", // 继承基础配置
"compilerOptions": { // 指定输出目录
"outDir": "../../dist/client",
},
"references": [ // 指定依赖工程
{ "path": "../common" }
]
}
4、build构建模式
–build/-b:单独构建一个工程,相关依赖会构建
–verbose:打印构建信息
tsc -b src/client --verbose // 构建client工程,打印构建信息
tsc -b src/client --clean // 清空client工程构建文件
24、TS编译工具
1、获取webpack环境变量
// webpack.config.js
// Wrong!
process.env.NODE_ENV === 'development' ? ...
module.exports = (env, argv) => {
argv.mode === 'development' ? ...
}
2、ts-loader
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sWUOlwdx-1638793440863)(C:\resourse\前端笔记\78-极客-typescript\images\编译时间对比.png)]
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.tsx?$/i,
use: [
{
// 内部调用TS编译器tsc,和官方编译器共享tsconfig.json配置文件
loader: 'ts-loader',
// ts-loader额外配置
options: {
// 默认false,开启后只做语言转换,不做类型检查
transpileOnly: true
}
}
],
exclude: /node_modules/
}
]
}
// 开启transpileOnly,进行类型检查
// 1 安装插件:npm i fork-ts-checker-webpack-plugin -D
// 2 导入:const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
// 3、使用插件
plugins: [
new ForkTsCheckerWebpackPlugin()
]
}
3、awesome-typescript-loader
与ts-loader的主要区别:
-
更适合与Babel集成,使用Babel的转义和缓存
-
不需要安装额外的额插件,就可以把类型检查放到独立的进程中
// webpack.config.js
const { checkerPlugin } = require('awesome-type')
module.exports = {
...
module: {
rules: [
{
test: /\.tsx?$/i,
use: [
{
// loader: 'ts-loader',
loader: 'awesome-typescript-loader'
options: {
transpileOnly: true
}
}
],
exclude: /node_modules/
}
]
}
plugins: [
// new ForkTsCheckerWebpackPlugin()
new CheckerPlugin()
]
}
4、typescript与Babel
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z8SkaOoE-1638793440864)(C:\resourse\前端笔记\78-极客-typescript\images\TS与Babel.png)]
Babel 7 之前:
TS -> tsc(ts-loader/awesome-typescript-loader) -> JS -> Babel -> JS
Babel 7 之后:
TS -> Babel -> JS
5、Babel
// package.json
{
"name": "ts_babel",
"version": "1.0.0",
"description": "",
"main": "./src/index.ts",
"scripts": {
"build": "babel src --out-dir dist --extensions \".ts,.tsx\""
},
"keywords": [
"TypeScript"
],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.5",
"@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/plugin-proposal-object-rest-spread": "^7.4.4", // 支持剩余和扩展操作符
"@babel/preset-env": "^7.4.5",
"@babel/preset-typescript": "^7.3.3", //编译TS文件
"typescript": "^3.5.2"
}
}
// .babelrc
{
"presets": [
"@babel/preset-env",
"@babel/preset-typescript"
],
"plugins": [
"@babel/proposal-class-properties",
"@babel/proposal-object-rest-spread"
]
}
// 添加类型检查
// 1、npm i typescript -D
// 2、tsc --init
"noEmit": true
// 3、package.json
{
"scripts": {
// 开启TS监控模式
"type-check": "tsc --watch"
}
}
// Babel无法编译的语法
// 1、命名空间
namespace N {
export const n = 1
}
// 2、类型断言
interface A {
a: number
}
let s = {} as A
s.a = 1
// 3、常量枚举
enum E { A }
// 4、默认导出
export = S
// 搭建webpack开发服务器
6、选择Typescript编译工具
1)没有使用过Babel,首选TypeScript自身的编译器(可配合ts-loader使用)
2)如果项目中已经使用了Babel,安装@babel/preset-typescript(可配合tsc做类型检查)
3)Babel和TS编译工具不要混用
25、TS代码检查工具
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E6v5U535-1638793440865)(C:\resourse\前端笔记\78-极客-typescript\images\TS代码检查工具.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZQBVgz4C-1638793440865)(C:\resourse\前端笔记\78-极客-typescript\images\ESLint2.png)]
ESLint抽象语法树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iC2dxgHy-1638793440866)(C:\resourse\前端笔记\78-极客-typescript\images\抽象语法树AST.png)]
TS中使用ESLint(typescript-eslint)
1、脚本作为类型检查
1.1、package.json
{
"name": "ts_base",
"version": "1.0.0",
"description": "",
"main": "./src/index.ts",
"scripts": {
"start": "webpack-dev-server --mode=development --config ./build/webpack.config.js",
"build": "webpack --mode=production --config ./build/webpack.config.js",
"lint": "eslint src --ext .js,.ts",
"test": "jest"
},
"keywords": [
"TypeScript"
],
"author": "liangxiao",
"license": "ISC",
"devDependencies": {
"@types/jest": "^24.0.15",
"@types/jquery": "^3.3.29",
"@types/source-map": "^0.5.2",
"@typescript-eslint/eslint-plugin": "^1.10.2", // 识别TS特殊语法
"@typescript-eslint/parser": "^1.10.2", // 为ESLint提供TS的解析器
"awesome-typescript-loader": "^5.2.1",
"clean-webpack-plugin": "^3.0.0",
"eslint": "^5.16.0",
"fork-ts-checker-webpack-plugin": "^1.3.7",
"html-webpack-plugin": "^3.2.0",
"jest": "^24.8.0",
"ts-jest": "^24.0.2",
"ts-loader": "^6.0.2",
"typescript": "^3.5.1",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.5.1",
"webpack-merge": "^4.2.1"
},
"dependencies": {
"jquery": "^3.4.1",
"moment": "^2.24.0"
}
}
1.2、.eslintrc.json
{
"parser": "@typescript-eslint/parser", // 指定解析器
"plugins": ["@typescript-eslint"], // 指定插件
"parserOptions": {
"project": "./tsconfig.json"
},
"extends": [
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@typescript-eslint/no-inferrable-types": "off" // 关闭类型推断
}
}
2、vscode插件(ESLint)
{
"window.zoomLevel": 2,
"explorer.confirmDelete": false,
"eslint.autoFixOnSave": true,
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "typescript",
"autoFix": true
},
{
"language": "html",
"autoFix": true
},
{
"language": "vue",
"autoFix": true
}
]
}
3、babel-eslint与typescript-eslint
babel-eslint:支持TypeScript,没有额外的语法检查,抛弃TypeScript,不持支类型检查
typescript-eslint:基于TypeScript的AST,支持创建基于类型信息的规则(tsconfig.json)
建议:两者底层机制不一样,不要一起使用。Babel体系建议使用babel-eslint,否则使用typescript-eslint
26、Jest单元测试
TS工具体系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vD9r8h26-1638793440866)(C:\resourse\前端笔记\78-极客-typescript\images\TS工具.png)]
1、package.json
{
"name": "ts_base",
"version": "1.0.0",
"description": "",
"main": "./src/index.ts",
"scripts": {
"start": "webpack-dev-server --mode=development --config ./build/webpack.config.js",
"build": "webpack --mode=production --config ./build/webpack.config.js",
"lint": "eslint src --ext .js,.ts",
"test": "jest" // 配置测试脚本
},
"keywords": [
"TypeScript"
],
"author": "liangxiao",
"license": "ISC",
"devDependencies": {
"@types/jest": "^24.0.15",
"@types/jquery": "^3.3.29",
"@types/source-map": "^0.5.2",
"@typescript-eslint/eslint-plugin": "^1.10.2",
"@typescript-eslint/parser": "^1.10.2",
"awesome-typescript-loader": "^5.2.1",
"clean-webpack-plugin": "^3.0.0",
"eslint": "^5.16.0",
"fork-ts-checker-webpack-plugin": "^1.3.7",
"html-webpack-plugin": "^3.2.0",
"jest": "^24.8.0", // 安装jest
"ts-jest": "^24.0.2", // 安装ts-jest
"ts-loader": "^6.0.2",
"typescript": "^3.5.1",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.5.1",
"webpack-merge": "^4.2.1"
},
"dependencies": {
"jquery": "^3.4.1",
"moment": "^2.24.0"
}
}
2、生成jest配置文件
npx ts-jest config:init
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node'
}
3、编写测试用例
// src/test/mat.test.ts
const math = require('../src/math')
test('add: 1 + 1 = 2', () => {
expect(math.add(1, 1).toBe(2)); // 断言
})
test('sub: 1 - 2 = -1', () => {
expect(math.sub(1, 2).toBe(-1))
})
// npm run test
4、babel-jest
4.1、安装babel-jest(测试用例同上)
npm i -D jest babel-jest @types/jest
{
"name": "ts_base",
"version": "1.0.0",
"description": "",
"main": "./src/index.ts",
"scripts": {
"start": "webpack-dev-server --mode=development --config ./build/webpack.config.js",
"build": "webpack --mode=production --config ./build/webpack.config.js",
"lint": "eslint src --ext .js,.ts",
"test": "jest" // 配置测试脚本
},
"keywords": [
"TypeScript"
],
"author": "liangxiao",
"license": "ISC",
"devDependencies": {
"@types/jest": "^24.0.15",
"@types/jquery": "^3.3.29",
"@types/source-map": "^0.5.2",
"@typescript-eslint/eslint-plugin": "^1.10.2",
"@typescript-eslint/parser": "^1.10.2",
"awesome-typescript-loader": "^5.2.1",
"clean-webpack-plugin": "^3.0.0",
"eslint": "^5.16.0",
"fork-ts-checker-webpack-plugin": "^1.3.7",
"html-webpack-plugin": "^3.2.0",
"jest": "^24.8.0", // 安装jest
"babel-jest": "^24.0.2", // 安装babel-jest
"ts-loader": "^6.0.2",
"typescript": "^3.5.1",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.5.1",
"webpack-merge": "^4.2.1"
},
"dependencies": {
"jquery": "^3.4.1",
"moment": "^2.24.0"
}
}
// 无类型检查
// npm run test
// 添加类型检查:启动类型检查脚本 npm run type-check
// require未定义:npm i @types/node -D
27、装饰器
装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器
装饰器的写法:普通装饰器(无法传参) 、 装饰器工厂(可传参)
装饰器是过去几年中js最大的成就之一,已是Es7的标准特性之一
- 类装饰器:普通装饰器(无法传参)
类装饰器:类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 传入一个参数
function logClass(params: any) {
console.log(params); // params为当前类
params.prototype.apiUrl = "动态扩展属性";
params.prototype.run = () => {
console.log('run');
}
}
@logClass
class HttpClient {
constructor() {}
getData(){}
}
let http = new HttpClient()
console.log(http.apiUrl); // 动态扩展属性
http.run(); // run
- 类装饰器:装饰器工厂(可传参)
funciton logClass(params: string) {
return function(target: any) { // 工厂匿名函数携带当前类
cnosole.log(target); // 当前类
console.log(params); // http://www.baidu.com
target.prototype.apiUrl = params;
}
}
@logClass('http://www.baidu.com')
class HttpClient {
constructor() {}
getData() {}
}
let http: any = new HttpClient()
console.log(http.apiUrl); // http://www.baidu.com
- 类装饰器:重载构造函数
function logClass(target: any) { // target为当前类
return class extends target { // 继承这个类可以直接修改属性和方法(必须重载属性和方法否则报错)
apiUrl: any = '修改后的数据';
getData() {
this.apiUrl = this.apiUrl + '---';
console.log(this.apiUrl);
}
}
}
@logClass
class HttpClient {
public apiUrl: string | undefined;
constructor() {
this.apiUrl = '构造函数中的apiUrl';
}
getData() {
console.log(this.apiUrl);
}
}
let http = new HttpClient();
console.log(http.apiUrl) // 修改后的数据
http.getData(); // 修改后的数据---
- 属性装饰器
属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
1、对于静态成员来说是类的构造函数(当前类),对于实例成员是类的原型对象2、成员的名字
// 类装饰器
function logClass(params: string) {
return function(target: any) {
console.log(params); // http://yyy.com
console.log(target); // 当前类
}
}
// 属性装饰器
function logProperty(params: any) {
return function(target: any, attr: any) {
console.log(params); // http://xxx.com
console.log(target); //当前类的原型对象
console.log(attr); // url
}
}
@logClass('http://yyy.com')
class HttpClient {
@logProperty('http://xxx.com')
public url: stirng | undefined;
constructor() {}
getData() { console.log(this.url); }
}
- 方法装饰器
应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。
方法装饰会在运行时传入下列3个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2、成员的名字。
3、成员的属性描述符。
function get(params: string) {
return function(target: any, methodName: string, desc: Record<string, any>) {
console.log(params); // 123
console.log(target); // 类的原型对象
console.log(methodName); // 方法名getData
console.log(desc) // { "writable": true, "enumerable": false, "configurable": true } (属性描述)
// 扩充当前实例的属性和方法
target.url = 'ddd';
target.run = () => {
console.log('run');
}
}
}
class HttpClient {
public url: string | undefined;
constructor() {}
@get('123')
getData() {
console.log(this.url);
}
}
let http = new HttpClient()
console.log(http.url); // ddd
http.run() // run
function get(params: string) {
return function(target: any, methodName: string, desc: Record<string,any>) {
console.log(params); // 123123
console.elog(target); // 当前类原型对象
console.log(methodName); // 方法名 getData
console.log(desc); // 方法描述信息
// 修改装饰器的方法:功能是要把装饰器方法里面传入的所有参数改为 string 类型, 类似于拦截器
let method = desc.value;
desc.value = function(...args: any[]) {
args = args.map(item => String(item));
method.apply(this, args)
}
}
}
class HttpClient {
constructor() {}
@get('123123')
getData(...args: any[]) {
console.log(args);
console.log('getData中的方法');
}
}
let http = new HttpClient()
http.getData(1, 2); // ['1', '2'] getData中的方法
- 方法参数装饰器
方法参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2、参数的名字
3、参数在函数参数列表中的索引
function logParams (params: string) {
return function(target: any, paramsName: string, index: number) {
console.log(params); // xxx
console.log(target); // 当前类的原型对象
console.log(paramsName); // id
console.log(index); 0
target.url = 'http://www.hahaha.com';
target.run = () => {
console.log('run');
}
}
}
class HttpClient {
public url: string | undefined;
constructor() {}
getData(@logParams('xxx')id: number) {
console.log(id);
}
}
let http = new HttpClient();
console.log(http.url); // http://www.hahaha.com
http.run(); // run
http.getData(11); // 11
- 装饰器执行顺序
属性装饰器 -> 方法装饰器 -> 方法参数装饰器 -> 类装饰器
如果有多个相同的装饰器,优先执行后边的装饰器
function logClass1(params: string) {
return function(target: any) {
console.log('类装饰器1')
}
}
function logClass2(params: string) {
return function(target: any) {
console.log('类装饰器2')
}
}
function logAttribute1(params: any) {
return function(target: any, attr: string) {
console.log('属性装饰器1')
}
function logAttribute2(params: any) {
return function(target: any, attr: string) {
console.log('属性装饰器2')
}
}
function logMethod1(params: any) {
return function(target: any, methodName: string, desc: any) {
console.log('方法装饰器1')
}
}
function logMethod2(params: any) {
return function(target: any, methodName: string, desc: any) {
console.log('方法装饰器2')
}
}
function logParams1(params: any) {
return function(target: any, paramsName: string, index: number) {
console.log('方法参数装饰器1');
}
}
function logParams(params: any) {
return function(target: any, paramsName: string, index: number) {
console.log('方法参数装饰器2');
}
}
@logClass1('http://www.baidu.com/api');
@logCLass2('xxx');
class HttpClient {
@logAttribute1()
@logAttribute2()
public apiUrl: string | undefined;
constructor() {}
@logMethod1()
@logMethod2()
getData() {
return true;
}
setData(@logParams1() attr1: any, @logParams2() attr2: any) {}
}
let http = new HttpClient();
// 属性装饰器2
// 属性装饰器1
// 方法装饰器2
// 方法装饰器1
// 方法参数装饰器2
// 方法参数装饰器1
// 类装饰器2
// 类装饰器1
28、Vue中使用TS
饰器表达式会在运行时当作函数被调用,传入下列3个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2、参数的名字
3、参数在函数参数列表中的索引
function logParams (params: string) {
return function(target: any, paramsName: string, index: number) {
console.log(params); // xxx
console.log(target); // 当前类的原型对象
console.log(paramsName); // id
console.log(index); 0
target.url = 'http://www.hahaha.com';
target.run = () => {
console.log('run');
}
}
}
class HttpClient {
public url: string | undefined;
constructor() {}
getData(@logParams('xxx')id: number) {
console.log(id);
}
}
let http = new HttpClient();
console.log(http.url); // http://www.hahaha.com
http.run(); // run
http.getData(11); // 11
- 装饰器执行顺序
属性装饰器 -> 方法装饰器 -> 方法参数装饰器 -> 类装饰器
如果有多个相同的装饰器,优先执行后边的装饰器
function logClass1(params: string) {
return function(target: any) {
console.log('类装饰器1')
}
}
function logClass2(params: string) {
return function(target: any) {
console.log('类装饰器2')
}
}
function logAttribute1(params: any) {
return function(target: any, attr: string) {
console.log('属性装饰器1')
}
function logAttribute2(params: any) {
return function(target: any, attr: string) {
console.log('属性装饰器2')
}
}
function logMethod1(params: any) {
return function(target: any, methodName: string, desc: any) {
console.log('方法装饰器1')
}
}
function logMethod2(params: any) {
return function(target: any, methodName: string, desc: any) {
console.log('方法装饰器2')
}
}
function logParams1(params: any) {
return function(target: any, paramsName: string, index: number) {
console.log('方法参数装饰器1');
}
}
function logParams(params: any) {
return function(target: any, paramsName: string, index: number) {
console.log('方法参数装饰器2');
}
}
@logClass1('http://www.baidu.com/api');
@logCLass2('xxx');
class HttpClient {
@logAttribute1()
@logAttribute2()
public apiUrl: string | undefined;
constructor() {}
@logMethod1()
@logMethod2()
getData() {
return true;
}
setData(@logParams1() attr1: any, @logParams2() attr2: any) {}
}
let http = new HttpClient();
// 属性装饰器2
// 属性装饰器1
// 方法装饰器2
// 方法装饰器1
// 方法参数装饰器2
// 方法参数装饰器1
// 类装饰器2
// 类装饰器1