TypeScript
是Javascript
的超集,遵循最新的ES5/ES6
规范。Typescript
扩展了Javascript
语法。
JS
可以开发大型企业应用
TypeScript
不会取代JS
, 尤雨溪: 我认为将类型添加到JS
本身是一个漫长的过程 。让委员会设计一个类型系统是(根据TC39
的经历来判断)不切实际的 。
全局安装typescript
对TS
进行编译
1 2 |
npm install typescript -g tsc --init # 生成tsconfig.json |
1 2 |
tsc # 可以将ts文件编译成js文件 tsc --watch # 监控ts文件变化生成js文件 |
webpack
环境
1 |
npm install rollup typescript rollup-plugin-typescript2 @rollup/plugin-node-resolve rollup-plugin-serve -D |
TS
配置文件
1 |
npx tsc --init |
webpack
配置操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// rollup.config.js import ts from 'rollup-plugin-typescript2' import {nodeResolve} from '@rollup/plugin-node-resolve'; import serve from 'rollup-plugin-serve'; import path from 'path' export default { input:'src/index.ts', output:{ format:'iife', file:path.resolve('dist/bundle.js'), sourcemap:true }, plugins:[ nodeResolve({ extensions:['.js','.ts'] }), ts({ tsconfig:path.resolve(__dirname,'tsconfig.json') }), serve({ open:true, openPage:'/public/index.html', port:3000, contentBase:'' }) ] } |
package.json
配置
1 2 3 |
"scripts": { "dev": "rollup -c -w" } |
我们可以通过
npm run start
启动服务来使用typescript啦~
TS中冒号后面的都为类型标识
1 2 3 |
let bool:boolean = true; let num:number = 10; let str:string = 'hello zf'; |
限制长度个数、类型一一对应
1 2 3 |
let tuple:[string,number,boolean] = ['zf',10,true]; // 像元组中增加数据,只能增加元组中存放的类型 tuple.push('回龙观'); |
声明数组中元素数据类型
1 2 3 4 |
let arr1:number[] = [1,2,3]; let arr2:string[] = ['1','2','3']; let arr3:(number|string)[] = [1,'2',3]; let arr4:Array<number | string> = [1,'2',3]; // 泛型方式来声明 |
1 2 3 4 5 6 |
enum USER_ROLE { USER, // 默认从0开始 ADMIN, MANAGER } // {0: "USER", 1: "ADMIN", 2: "MANAGER", USER: 0, ADMIN: 1, MANAGER: 2} |
可以枚举,也可以反举
1 2 3 4 5 6 |
// 编译后的结果 (function (USER_ROLE) { USER_ROLE[USER_ROLE["USER"] = 0] = "USER"; USER_ROLE[USER_ROLE["ADMIN"] = 1] = "ADMIN"; USER_ROLE[USER_ROLE["MANAGER"] = 2] = "MANAGER"; })(USER_ROLE || (USER_ROLE = {})); |
1 2 3 4 5 |
enum USER_ROLE { USER = 'user', ADMIN = 1, MANAGER, } |
1 2 3 4 5 6 |
const enum USER_ROLE { USER, ADMIN, MANAGER, } console.log(USER_ROLE.USER)// console.log(0 /* USER */); |
不进行类型检测
1 |
let arr:any = ['jiagou',true,{name:'zf'}] |
任何类型的子类型,如果strictNullChecks
的值为true,则不能把null 和 undefined付给其他类型
1 2 |
let name:number | boolean; name = null; |
只能接受null,undefined。一般用于函数的返回值
1 2 |
let a:void; a = undefined; |
严格模式下不能将
null
赋予给void
任何类型的子类型,never代表不会出现的值。不能把其他类型赋值给never
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function error(message: string): never { throw new Error("err"); } function loop(): never { while (true) { } } function fn(x:number | string){ if(typeof x == 'number'){ }else if(typeof x === 'string'){ }else{ console.log(x); // never } } |
Symbol表示独一无二
1 2 3 |
const s1 = Symbol('key'); const s2 = Symbol('key'); console.log(s1 == s2); // false |
BigInt
类型
1 2 3 4 5 6 7 |
const num1 = Number.MAX_SAFE_INTEGER + 1; const num2 = Number.MAX_SAFE_INTEGER + 2; console.log(num1 == num2)// true let max: bigint = BigInt(Number.MAX_SAFE_INTEGER) console.log(max + BigInt(1) === max + BigInt(2)) |
number
类型和bigInt
类型是不兼容的
object
表示非原始类型
1 2 3 4 |
let create = (obj:object):void=>{} create({}); create([]); create(function(){}) |
any
类型
1 2 3 |
let name; // 类型为any name = 'wj' name = 10; |
1 2 |
let name = 'wj'; // name被推导为字符串类型 name = 10; |
我们在使用基本数据类型时,调用基本数据类型上的方法,默认会将原始数据类型包装成对象类型
1 2 3 |
let bool1:boolean = true; let bool2:boolean = Boolean(1); let bool3:Boolean = new Boolean(2); |
boolean是基本数据类型 , Boolean是他的封装类
在使用联合类型时,没有赋值只能访问联合类型中共有的方法和属性
1 2 3 4 5 6 |
let name:string | number // 联合类型 console.log(name!.toString()); // 公共方法 name = 10; console.log(name!.toFixed(2)); // number方法 name = 'zf'; console.log(name!.toLowerCase()); // 字符串方法 |
这里的!表示此值非空
1 2 |
let ele: HTMLElement | null = document.getElementById('#app'); ele!.style.color = 'red'; // 断定ele元素一定有值 |
1 2 3 |
let name: string | number; (name! as number).toFixed(2); // 强制 ((<number>name!).toFixed(2)); |
尽量使用第一种类型断言因为在react中第二种方式会被认为是
jsx
语法
1 2 |
let name: string | boolean; ((name! as any) as string); |
尽量不要使用双重断言,会破坏原有类型关系,断言为any是因为any类型可以被赋值给其他类型
1 2 |
type Direction = 'Up' | 'Down' | 'Left' | 'Right'; let direction:Direction = 'Down'; |
可以用字面量当做类型,同时也表明只能采用这几个值(限定值)。类似枚举。
1 2 3 4 |
function sum(a: string, b: string):string { return a+b; } sum('a','b') |
可以用来限制函数的参数和返回值类型
1 2 3 4 |
type Sum = (a1: string, b1: string) => string; let sum: Sum = (a: string, b: string) => { return a + b; }; |
1 2 3 4 |
let sum = (a: string, b?: string):string => { return a + b; }; sum('a'); // 可选参数必须在其他参数的最后面 |
1 2 3 4 |
let sum = (a: string, b: string = 'b'): string => { return a + b; }; sum('a'); // 默认参数必须在其他参数的最后面 |
1 2 3 4 |
const sum = (...args: string[]): string => { return args.reduce((memo, current) => memo += current, '') } sum('a', 'b', 'c', 'd') |
1 2 3 4 5 6 7 8 9 10 11 |
function toArray(value: number): number[] function toArray(value: string): string[] function toArray(value: number | string) { if (typeof value == 'string') { return value.split(''); } else { return value.toString().split('').map(item => Number(item)); } } toArray(123); // 根据传入不同类型的数据 返回不同的结果 toArray('123'); |
1 2 3 4 5 6 7 8 9 |
class Pointer{ x!:number; // 实例上的属性必须先声明 y!:number; constructor(x:number,y?:number,...args:number[]){ this.x = x; this.y = y as number; } } let p = new Pointer(100,200); |
实例上的属性需要先声明在使用,构造函数中的参数可以使用可选参数和剩余参数
public
修饰符(谁都可以访问到)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Animal { public name!: string; // 不写public默认也是公开的 public age!: number; constructor(name: string, age: number) { this.name = name; this.age = age; } } class Cat extends Animal { constructor(name: string, age: number) { super(name, age); console.log(this.name,this.age); // 子类访问 } } let p = new Cat('Tom', 18); console.log(p.name,p.age); // 外层访问 |
1 2 3 4 5 6 |
class Animal { constructor(public name: string, public age: number) { this.name = name; this.age = age; } } |
我们可以通过参数属性来简化父类中的代码
protected
修饰符 (自己和子类可以访问到)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Animal { constructor(protected name: string, protected age: number) { this.name = name; this.age = age; } } class Cat extends Animal { constructor(name: string, age: number) { super(name, age); console.log(this.name, this.age) } } let p = new Cat('Tom', 18); console.log(p.name,p.age);// 无法访问 |
private
修饰符 (除了自己都访问不到)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Animal { constructor(private name: string, private age: number) { this.name = name; this.age = age; } } class Cat extends Animal { constructor(name: string, age: number) { super(name, age); console.log(this.name, this.age); // 无法访问 } } let p = new Cat('Tom', 18); console.log(p.name,p.age);// 无法访问 |
readonly
修饰符 (仅读修饰符)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Animal { constructor(public readonly name: string, public age: number) { this.name = name; this.age = age; } changeName(name:string){ this.name = name; // 仅读属性只能在constructor中被赋值 } } class Cat extends Animal { constructor(name: string, age: number) { super(name, age); } } let p = new Cat('Tom', 18); p.changeName('Jerry'); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Animal { static type = '哺乳动物'; // 静态属性 static getName() { // 静态方法 return '动物类'; } private _name: string = 'Tom'; get name() { // 属性访问器 return this._name; } set name(name: string) { this._name = name; } } let animal = new Animal(); console.log(animal.name); |
静态属性和静态方法是可以被子类所继承的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Animal { say(message:string){ console.log(message); } static getType(){ return '动物' } } class Cat extends Animal { say(){ // 原型方法中的super指代的是父类的原型 super.say('猫猫叫'); } static getType(){ // 静态方法中的super指代的是父类 return super.getType() } } let cat = new Cat(); console.log(Cat.getType()) |
1 2 3 4 5 6 7 8 9 |
function addSay(target:any){ target.prototype.say = function(){console.log('say')} } @addSay class Person { say!:Function } let person = new Person person.say(); |
装饰类可以给类扩展功能,需要开启
experimentalDecorators:true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
function toUpperCase(target:any,key:string){ let value = target[key]; Object.defineProperty(target,key,{ get(){ return value.toUpperCase(); }, set(newValue){ value = newValue } }) } function double(target: any, key: string) { let value = target[key]; Object.defineProperty(target, key, { get() { return value * 2; }, set(newValue) {value = newValue} }) } class Person { @toUpperCase name: string = 'JiangWen' @double static age: number = 10; getName() { return this.name; } } let person = new Person(); console.log(person.getName(),Person.age) |
装饰属性可以对属性的内容进行改写,装饰的是实例属性则target指向类的原型、装饰的是静态属性则target执行类本身~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function noEnum(target:any,key:string,descriptor:PropertyDescriptor){ console.log(descriptor) descriptor.enumerable = false; } class Person { @toUpperCase name: string = 'JiangWen' @double static age: number = 10; @noEnum getName() { return this.name; } } let person = new Person(); console.log(person); // getName 不可枚举 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function addPrefix(target:any,key:string,paramIndex:number){ console.log(target,key,paramIndex); // Person.prototype getName 0 } class Person { @toUpperCase name: string = 'JiangWen' @double static age: number = 10; prefix!:string @noEnum getName(@addPrefix prefix:string) { return this.name; } } |
抽象类无法被实例化,只能被继承,抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
abstract class Animal{ name!:string; abstract speak():void } class Cat extends Animal { speak(){ console.log('猫猫叫'); } } class Dog extends Animal{ speak():string{ console.log('汪汪叫'); return 'wangwang' } } |
定义类型时
void
表示函数的返回值为空(不关心返回值类型,所有在定义函数时也不关心函数返回值类型)
接口可以在面向对象编程中表示行为的抽象,也可以描述对象的形状。 接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。 (接口中不能含有具体的实现逻辑)
1 2 3 |
const fullName = ({firstName,lastName}:{firstName:string,lastName:string}):string =>{ return firstName + lastName } |
我们可以约束函数中的参数,但是类型无法复用
1 2 3 4 5 6 7 |
interface IFullName { firstName:string, lastName:string } const fullName = ({firstName,lastName}:IFullName):string =>{ return firstName + lastName } |
我们可以通过接口进行描述
1 2 3 4 5 6 7 8 9 10 |
interface IFullName { firstName:string, lastName:string } interface IFn { (obj:IFullName):string } const fullName:IFn = ({firstName,lastName})=>{ return firstName + lastName } |
通过接口限制函数的参数类型和返回值类型
1 2 3 4 5 6 7 8 9 10 11 12 |
interface ICounter { (): number; // 限制函数类型 count: 0 // 限制函数上的属性 } let fn: any = () => { fn.count++; return fn.count; } fn.count = 0; let counter:ICounter = fn; console.log(counter()); console.log(counter()); |
对象接口可以用来描述对象的形状结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
interface IVegetables { readonly color:string, size:string } interface IVegetables{ age?:number, taste:'sour'|'sweet' } const tomato:IVegetables = { color:'red', size:'10', taste:'sour' } tomato.color = 'green'; // 仅读属性不能进行修改 |
?标识的属性为可选属性,
readOnly
标识的属性则不能修改。多个同名的接口会自动合并
1 2 3 4 5 6 |
const tomato:IVegetables = { color:'red', size:'10', taste:'sour', type:'蔬菜' } as IVegetables; // 多余的属性可以使用类型断言 |
1 2 3 4 5 6 7 8 9 |
interface Person { name: string; [key: string]: any } let p: Person = { name: 'wj', age: 10, [Symbol()]:'回龙观' } |
任意属性可以对某一部分必填属性做限制,其余的可以随意增减
1 2 3 4 5 6 7 |
interface IArr { [key: number]: any } let p: IArr = { 0:'1',1:'2',3:'3' } let arr:IArr = [1,'d','c']; |
可索引接口可以用于标识数组
这里先来强调一下抽象类和接口的区别,抽象类中可以包含具体方法实现。接口中不能包含实现
1 2 3 4 5 6 7 8 9 10 11 12 |
interface Speakable { name:string; speak():void; } interface ChineseSpeakable{ speakChinese():void } class Speak implements Speakable,ChineseSpeakable{ name!:string speak(){} speakChinese(){} } |
一个类可以实现多个接口,在类中必须实现接口中的方法和属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
interface Speakable { speak():void } interface SpeakChinese extends Speakable{ speakChinese():void } class Speak implements SpeakChinese{ speakChinese(): void { throw new Error("Method not implemented."); } speak(): void { throw new Error("Method not implemented."); } } |
1 2 3 4 5 6 7 8 9 10 11 12 |
interface Clazz { new (name:string):any } function createClass(target:Clazz,name:string){ return new target(name); // 传入的是一个构造函数 } class Animal { constructor(public name:string){ this.name = name; } } let r = createClass(Animal,'Tom'); |
这里无法标识返回值类型
1 2 3 4 5 6 7 8 9 10 11 12 |
interface Clazz<T> { new(name: string): T } function createClass<T>(target: Clazz<T>, name: string):T { return new target(name) } class Animal { constructor(public name: string) { this.name = name; } } let r = createClass(Animal, 'Tom'); |
new() 表示当前是一个构造函数类型,这里捎带使用了下泛型。 在使用
createClass
时动态传入类型。
1 2 3 4 5 6 7 8 |
const getArray = <T>(times:number,val:T):T[]=>{ let result:T[] = []; for(let i = 0; i<times;i++){ result.push(val); } return result; } getArray(3,3); // 3 => T => number |
1 2 3 4 |
function swap<T, K>(tuple: [T, K]): [K, T] { return [tuple[1], tuple[0]] } console.log(swap(['a','b'])) |
1 2 3 4 |
type TArray = <T, K>(tuple: [T, K]) => [K, T]; const getArray:TArray = <T, K>(tuple: [T, K]): [K, T] => { return [tuple[1], tuple[0]] } |
可以使用类型别名,但是类型别名不能被继承和实现。一般联合类型可以使用类型别名来声明
1 2 3 4 5 6 |
interface IArray{ <T,K>(typle:[T,K]):[K,T] } const getArray:IArray = <T, K>(tuple: [T, K]): [K, T] => { return [tuple[1], tuple[0]] } |
能使用interface尽量使用interface
1 2 3 4 5 6 |
interface ISum<T> { // 这里的T是使用接口的时候传入 <U>(a: T, b: T): U // 这里的U是调用函数的时候传入 } let sum: ISum<number> = (a:number, b:number) => { return 3 as any } |
1 2 3 4 5 |
interface T2<T=string>{ name:T } type T22 = T2; let name1:T22 = {name:'zf'} |
可以指定泛型的默认类型,方便使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class MyArray<T>{ // T => number arr: T[] = []; add(num: T) { this.arr.push(num); } getMaxNum(): T { let arr = this.arr let max = arr[0]; for (let i = 1; i < arr.length; i++) { let current = arr[i]; current > max ? max = current : null } return max; } } let myArr = new MyArray<number>(); myArr.add(3); myArr.add(1); myArr.add(2); console.log(myArr.getMaxNum()); |
1 2 3 4 |
const createClass = <T>(clazz: new(name:string,age:number)=>T):T =>{ return new clazz(name,age); } createClass<Person2>(Person2) |
1 2 3 4 5 6 7 |
interface IWithLength { length:number } function getLen<T extends IWithLength>(val:T){ return val.length; } getLen('hello'); |
1 2 3 4 |
const sum = <T extends number>(a: T, b: T): T => { return (a + b) as T } let r = sum<number>(1, 2); |
1 2 3 |
const getVal = <T,K extends keyof T>(obj:T,key:K) : T[K]=>{ return obj[key]; } |
TS中的兼容性,主要看结构是否兼容。(核心是考虑安全性)
1 2 3 |
let temp:string | number; let num!:number; temp = num; |
你要的我有就可以
1 2 3 4 5 |
let num:{ toString():string } let str:string = 'zf'; num = str; // 字符串中具备toString()方法,所以可以进行兼容 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
interface IAnimal { name: string, age: number } interface IPerson { name: string, age: number, address: string } let animal: IAnimal; let person: IPerson = { name: 'zf', age: 11, address: '回龙观' }; animal = person; |
接口的兼容性,只要满足接口中所需要的类型即可!
函数的兼容性主要是比较参数和返回值
1 2 3 |
let sum1 = (a: string, b: string) => a + b; let sum2 = (a: string) => a; sum1 = sum2 |
赋值函数的参数要少于等于被赋值的函数,与对象相反,例如:
1 2 3 4 5 6 7 8 9 |
type Func<T> = (item: T, index: number) => void function forEach<T>(arr: T[], cb: Func<T>) { for (let i = 0; i < arr.length; i++) { cb(arr[i], i); } } forEach([1, 2, 3], (item) => { console.log(item); }); |
1 2 3 4 5 6 |
type sum1 = () => string | number type sum2 = () => string; let fn1: sum1; let fn2!: sum2; fn1 = fn2; |
函数的参数是逆变的,返回值是协变的 (在非严格模式下函数的参数是双向协变的)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Parent { address: string = '回龙观' } class Child extends Parent { money: number = 100 } class Grandsom extends Child { name: string = '吉姆'; } type Callback = (person: Child) => Child function execCallback(cb: Callback) { } let fn = (person: Parent) => new Grandsom; execCallback(fn); |
通过这个案例可以说明,函数参数可以接收父类,返回值可以返回子类
1 2 3 4 5 6 7 8 9 |
class Perent { name: string = 'zf'; age: number = 11 } class Parent1 { name: string = 'zf'; age: number = 11 } let parent: Perent = new Parent1 |
这里要注意的是,只要有private或者protected关键字类型就会不一致;但是继承的类可以兼容
1 2 3 4 5 6 |
class Parent1 { protected name: string = 'zf'; age: number = 11 } class Child extends Parent1{} let child:Parent1 = new Child; |
1 2 3 4 |
interface IT<T>{} let obj1:IT<string>; let obj2!:IT<number>; obj1 = obj2; |
1 2 3 4 5 6 7 8 9 |
enum USER1 { role = 1 } enum USER2 { role = 1 } let user1!:USER1 let user2!:USER2 user1 = user2 // 错误语法 |
通过判断识别所执行的代码块,自动识别变量属性和方法
typeof
类型保护
1 2 3 4 5 6 7 |
function double(val: number | string) { if (typeof val === 'number') { val } else { val } } |
instanceof
类型保护
1 2 3 4 5 6 7 8 9 10 11 12 |
class Cat { } class Dog { } const getInstance = (clazz: { new(): Cat | Dog }) => { return new clazz(); } let r = getInstance(Cat); if(r instanceof Cat){ r }else{ r } |
in
类型保护
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
interface Fish { swiming: string, } interface Bird { fly: string, leg: number } function getType(animal: Fish | Bird) { if ('swiming' in animal) { animal // Fish } else { animal // Bird } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
interface WarningButton { class: 'warning' } interface DangerButton { class: 'danger' } function createButton(button: WarningButton | DangerButton) { if (button.class == 'warning') { button // WarningButton } else { button // DangerButton } } |
1 2 3 4 5 6 7 8 |
const addPrefix = (num?: number) => { num = num || 1.1; function prefix(fix: string) { return fix + num?.toFixed() } return prefix('zf'); } console.log(addPrefix()); |
这里要注意的是ts无法检测内部函数变量类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
interface Fish { swiming: string, } interface Bird { fly: string, leg: number } function isBird(animal: Fish | Bird):animal is Bird { return 'swiming' in animal } function getAniaml (animal:Fish | Bird){ if(isBird(animal)){ animal }else{ animal } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
interface ICircle { kind: 'circle', r: number } interface IRant { kind: 'rant', width: number, height: number } interface ISquare { kind: 'square', width: number } type Area = ICircle | IRant | ISquare const isAssertion = (obj: never) => { } const getArea = (obj: Area) => { switch (obj.kind) { case 'circle': return 3.14 * obj.r ** 2 default: return isAssertion(obj); // 必须实现所有逻辑 } } |
赋值时推断,类型从右像左流动,会根据赋值推断出变量类型
1 2 3 |
let str = 'wj'; let age = 11; let boolean = true; |
自动推断函数返回值类型
1 2 3 4 |
function sum(a: string, b: string) { return a + b; } sum('a','b'); |
函数从左到右进行推断
1 2 |
type Sum = (a: string, b: string) => string; const sum: Sum = (a, b) => a + b; |
可以通过属性值,推断出属性的类型
1 2 3 4 5 |
let person = { name:'zf', age:11 } let {name,age} = person; |
可以使用typeof
关键字反推变量类型
1 2 3 4 5 |
let person = { name:'zf', age:11 } type Person = typeof person |
1 2 3 4 5 6 7 8 |
interface IPerson { name:string, age:number, job:{ address:string } } type job = IPerson['job'] |
1 2 3 4 5 |
interface IPerson { name:string, age:number } type MapPerson = {[key in keyof IPerson]:IPerson[key]} |
交叉类型(Intersection Types)是将多个类型合并为一个类型
1 2 3 4 5 6 7 8 |
interface Person1 { handsome: string, } interface Person2 { high: string, } type P1P2 = Person1 & Person2; let p: P1P2 = { handsome: '帅', high: '高' } |
举例:我们提供两拨人,一拨人都很帅、另一拨人很高。我们希望找到他们的交叉部分 => 又高又帅的人
1 2 3 4 |
function mixin<T, K>(a: T, b: K): T & K { return { ...a, ...b } } const x = mixin({ name: 'zf' }, { age: 11 }) |
1 2 3 4 5 6 7 8 9 10 11 12 |
interface IPerson1 { name:string, age:number } interface IPerson2 { name:number age:number } type person = IPerson1 & IPerson2 let name!:never let person:person = {name,age:11}; // 两个属性之间 string & number的值为never |
可以使用extends
关键字和三元表达式,实现条件判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
interface Fish { name1: string } interface Water { name2: string } interface Bird { name3: string } interface Sky { name4: string } type Condition<T> = T extends Fish ? Water : Sky; let con1: Condition<Fish> = { name2: '水' } |
1 |
let con2: Condition<Fish|Bird> = { name2: '水' } |
这里会用每一项依次进行分发,最终采用联合类型作为结果,等价于:
1 2 3 |
type c1 = Condition<Fish>; type c2 = Condition<Bird>; type c = c1 | c2 |
Exclude
排除类型
1 2 |
type Exclude<T, U> = T extends U ? never : T; type MyExclude = Exclude<'1' | '2' | '3', '1' | '2'> |
Extract
抽取类型
1 2 |
type Extract<T, U> = T extends U ? T : never; type MyExtract = Extract<'1' | '2' | '3', '1' | '2'> |
NoNullable
非空检测
1 2 |
type NonNullable<T> = T extends null | undefined ? never : T type MyNone = NonNullable<'a' | null | undefined> |
ReturnType
返回值类型
1 2 3 4 5 |
function getUser(a: number, b: number) { return { name: 'zf', age: 10 } } type ReturnType<T> = T extends (...args: any) => infer R ? R : never type MyReturn = ReturnType<typeof getUser> |
Parameters
参数类型
1 2 |
type Parameters<T> = T extends (...args: infer R) => any ? R : any; type MyParams = Parameters<typeof getUser>; |
ConstructorParameters
构造函数参数类型
1 2 3 4 5 |
class Person { constructor(name: string, age: number) { } } type ConstructorParameters<T> = T extends { new(...args: infer R): any } ? R : never type MyConstructor = ConstructorParameters<typeof Person> |
InstanceType
实例类型
1 2 |
type InstanceType<T> = T extends { new(...args: any): infer R } ? R : any type MyInstance = InstanceType<typeof Person> |
将数组类型转化为联合类型
1 2 |
type ElementOf<T> = T extends Array<infer E> ? E : never; type TupleToUnion = ElementOf<[string, number, boolean]>; |
将两个函数的参数转化为交叉类型
1 2 3 4 |
type T1 = { name: string }; type T2 = { age: number }; type ToIntersection<T> = T extends ([(x: infer U) => any, (x: infer U) => any]) ? U : never; type t3 = ToIntersection<[(x:T1)=>any,(x:T2)=>any]> |
表示要把
T1
、T2
赋予给x,那么x的值就是T1
、T2
的交集。(参数是逆变的可以传父类)
TS的类型:TS主要是为了代码的安全性来考虑。所以所有的兼容性问题都要从安全性来考虑!
1 2 3 4 5 6 7 8 9 10 11 |
interface Company { num: number } interface Person { name: string, age: string, company: Company } // type Partial<T> = { [K in keyof T]?: T[K] }; 实现原理 type PartialPerson = Partial<Person>; |
遍历所有的属性将属性设置为可选属性,但是无法实现深度转化!
1 2 3 4 5 |
type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] } type DeepPartialPerson = DeepPartial<Person>; |
我们可以实现深度转化,如果值是对象继续深度转化。
1 2 3 4 5 6 7 8 9 10 11 12 |
interface Company { num: number } interface Person { name: string, age: string, company: Company } type PartialPerson = Partial<Person>; type Required<T> = {[K in keyof T]-?:T[K]} type RequiredPerson = Required<PartialPerson> |
将所有的属性转化成必填属性
1 2 3 |
type Readonly<T> = { readonly [K in keyof T]: T[K] } type RequiredPerson = Readonly<Person> |
将所有属性变为仅读状态
1 2 3 |
type Pick<T, U extends keyof T> = { [P in U]: T[P] } type PickPerson = Pick<Person, 'name' | 'age'> |
在已有类型中挑选所需属性
1 2 3 |
type Record<K extends keyof any, T> = { [P in K] : T } let person: Record<string, any> = { name: 'zf', age: 11 }; |
实现map方法,我们经常用record类型表示映射类型
1 2 3 4 5 6 7 8 9 10 11 |
function map<T extends keyof any, K, U>(obj: Record<T, K>, callback: (item: K, key: T) => U) { let result = {} as Record<T, U> for (let key in obj) { result[key] = callback(obj[key], key) } return result } const r = map({ name: 'zf', age: 11 }, (item, key) => { return item }); |
1 2 3 4 5 6 7 8 |
let person = { name: 'wj', age: 11, address: '回龙观' } type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> type OmitAddress = Omit<typeof person, 'address'> |
忽略person中的address属性 (先排除掉不需要的key,在通过key选出需要的属性)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
type Proxy<T> = { get():T, set(value:T):void } type Proxify<T> = { [P in keyof T]: Proxy<T[P]> } let props = { name: 'wj', age: 11 } function proxify<T>(obj:T):Proxify<T>{ let result = {} as Proxify<T>; for(let key in obj){ let value = obj[key]; result[key] = { get(){ return value }, set:(newValue)=>value = newValue } } return result } let proxpProps = proxify(props); |
1 2 3 4 5 6 7 8 9 10 11 |
function unProxify<T>(proxpProps:Proxify<T>):T{ let result = {} as T; for(let key in proxpProps){ let value = proxpProps[key]; result[key] = value.get() } return result } let proxy = unProxify(proxpProps) |
求两个对象不同的部分
1 2 3 4 5 6 7 8 9 10 11 |
let person1 = { name: 'wj', age: 11, address: '回龙观' } let person2 = { address: '回龙观', } type Diff<T extends object,K extends Object> = Omit<T,keyof K> type DiffPerson = Diff<typeof person1,typeof person2> |
1 2 3 4 5 6 7 8 9 10 11 |
let person1 = { name: 'wj', age: 11, address: '回龙观' } let person2 = { address: '回龙观', } type InterSection<T extends object, K extends object> = Pick<T, Extract<keyof T, keyof K>> type InterSectionPerson = InterSection<typeof person1, typeof person2> |
1 2 3 4 5 6 7 8 |
type OldProps = { name: string, age: number, visible: boolean }; type NewProps = { age: string, other: string }; type Diff<T extends object,K extends Object> = Omit<T,keyof K> type InterSection<T extends object, K extends object> = Pick<T, Extract<keyof T, keyof K>> type Overwrite<T extends object, K extends object, I = Diff<T,K> & InterSection<K,T>> = Pick<I,keyof I> type ReplaceProps = Overwrite<OldProps, NewProps> |
如果存在已有属性则使用新属性类型进行覆盖操作
1 2 3 4 |
type Compute<A extends any> = { [K in keyof A]: A[K] }; type Merge<T, K> = Compute<Omit<T, keyof K> & K>; type MergeObj = Merge<OldProps,NewProps> |
将两个对象类型进行合并操作
unknown
类型,任何类型都可以赋值为unknown
类型。 它是 any 类型对应的安全类型
1 2 3 4 |
let unknown:unknown; unknown = 'zf'; unknown = 11; |
不能访问unknown类型上的属性,不能作为函数、类来使用
unknown
1 2 |
type UnionUnknown = unknown | null | string | number |
联合类型与
unknown
都是unknown
类型
unknown
1 2 |
type inter = unknown & null |
交叉类型与
unknown
都是其他类型
1 2 |
type isNever = never extends unknown ? true : false;= |
1 2 |
type key = keyof unknown; |
1 2 3 4 5 |
type IMap<T> = { [P in keyof T]:number } type t = IMap<unknown>; |
unknown类型不能和number类型进行
+
运算,可以用于等或不等操作
默认情况下 ,我们编写的代码处于全局命名空间中
文件模块: 如果在你的 TypeScript 文件的根级别位置含有 import 或者 export,那么它会在这个文件中创建一个本地的作用域 。
1 2 3 4 5 6 |
// a.ts导出 export default 'zf' // index.ts导入 import name from './a' |
命名空间可以用于组织代码,避免文件内命名冲突
1 2 3 4 5 6 7 8 9 10 11 12 |
export namespace zoo { export class Dog { eat() { console.log('zoo dog'); } } } export namespace home { export class Dog { eat() { console.log('home dog'); } } } let dog_of_zoo = new zoo.Dog(); dog_of_zoo.eat(); let dog_of_home = new home.Dog(); dog_of_home.eat(); |
1 2 3 4 5 6 7 8 |
export namespace zoo { export class Dog { eat() { console.log('zoo dog'); } } export namespace bear{ export const name = '熊' } } console.log(zoo.bear.name); |
命名空间中导出的变量可以通过命名空间使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
declare let age: number; declare function sum(a: string, b: string): void; declare class Animal { }; declare const enum Seaons{ Spring, Summer, Autumn, Winter } declare interface Person { name:string, age:number } |
类型声明在编译的时候都会被删除,不会影响真正的代码。目的是不重构原有的js代码,而且可以得到很好的TS支持
练习: 声明jQuery类型
jquery通过外部CDN方式引入,想在代码中直接使用
1 2 3 4 5 6 |
declare const $:(selector:string)=>{ height(num?:number):void width(num?:number):void }; $('').height(); |
1 2 3 4 5 6 7 8 9 |
declare namespace jQuery { function ajax(url:string,otpions:object):void; namespace fn { function extend(obj:object):void } } jQuery.ajax('/',{}); jQuery.fn.extend({}); |
namespace
表示一个全局变量包含很多子属性 , 命名空间内部不需要使用 declare 声明属性或方法
类型声明文件以
.d.ts
结尾。默认在项目编译时会查找所有以.d.ts
结尾的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// jquery.d.ts declare const $:(selector:string)=>{ height(num?:number):void width(num?:number):void }; declare namespace jQuery { function ajax(url:string,otpions:object):void; namespace fn { function extend(obj:object):void } } |
配置tsconfig.json
1 2 3 4 5 6 |
"moduleResolution": "node", "baseUrl": "./", "paths": { "*": ["types/*"] }, |
1 2 3 4 5 6 7 8 |
// types/jquery/index.d.ts declare function jQuery(selector: string): HTMLElement; declare namespace jQuery { function ajax(url: string): void } export = jQuery; |
1 2 3 4 5 6 7 |
import { EventEmitter } from "zf-events"; var e = new EventEmitter(); e.on('message', function (text) { console.log(text) }) e.emit('message', 'hello'); |
1 2 3 4 5 6 7 8 9 10 11 |
export type Listener = (...args: any[]) => void; export type Type = string | symbol export class EventEmitter { static defaultMaxListeners: number; emit(type: Type, ...args: any[]): boolean; addListener(type: Type, listener: Listener): this; on(type: Type, listener: Listener): this; once(type: Type, listener: Listener): this; } |
1 2 3 4 5 6 7 8 |
import $ from 'jquery' // 只适用于 export default $ const $ = require('jquery'); // 没有声明文件可以直接使用 require语法 import * as $ from 'jquery' // 为了支持 Commonjs规范 和 AMD规范 导出时采用export = jquery import $ = require('jquery') // export = jquery 在commonjs规范中使用 |
@types是一个约定的前缀,所有的第三方声明的类型库都会带有这样的前缀
1 2 |
npm install @types/jquery -S |
当使用jquery时默认会查找
node_modules/@types/jquery/index.d.ts
文件
可以直接使用接口对已有类型进行扩展
1 2 3 4 5 6 7 8 |
interface String { double():string } String.prototype.double = function () { return this as string + this; } let str = 'wj'; |
1 2 3 4 5 |
interface Window { mynane:string } console.log(window.mynane) |
1 2 3 4 5 6 7 8 9 |
declare global{ interface String { double():string; } interface Window{ myname:string } } |
声明全局表示对全局进行扩展
同一名称的两个独立声明会被合并成一个单一声明,合并后的声明拥有原先两个声明的特性。
1 2 3 4 5 6 7 8 |
interface Animal { name:string } interface Animal { age:number } let a:Animal = {name:'zf',age:10}; |
1 2 3 4 5 |
class Form {} namespace Form { export const type = 'form' } |
1 2 3 4 5 |
function getName(){} namespace getName { export const type = 'form' } |
1 2 3 4 5 6 7 8 9 |
enum Seasons { Spring = 'Spring', Summer = 'Summer' } namespace Seasons{ export let Autum = 'Autum'; export let Winter = 'Winter' } |
1 2 3 4 5 6 |
import { createStore, Store } from 'redux'; type StoreWithExt = Store & { ext:string } let store:StoreWithExt |
配置
tsconfig.json
为true 生成声明文件
1 2 |
"declaration": true |
以上就是 TypeScript
基础的全部啦,掌握了这些对于面试已经是绰绰有余,不过对于语言来说还是需要多加练习哟。欢迎点赞收藏 !
作者:我_舅是太阳
链接:https://juejin.cn/post/7102384712504573982