前断时间老项目升级 ts,一边看文档,一边实践,ts
基础语法非常简单,但是写好ts
就非常不简单,typescript
严格来讲算是一门强类型语言,它赋予js
类型体系,让开发者写js
更加严谨,并且它具备强大的类型推断,并且能在node
和浏览器
中运行。对于项目而言,使用typescript对提升项目的规范与严谨性更加友好。
本文只做笔者项目中常遇到的一些ts
经验,希望在项目中你能用得到。
正文开始...
定义常用类型
type[string|number|boolean|Array|Object|Function]
// string type NameType = string; const nameStr: NameType = 'Maic'; // const nameStr: string //or const nameStr2: string = 'tom'; // number type AgeType = number; const age:AgeType = 18; // or const age2: number = 20; const age2: AgeType = '';// const age: number 不能将类型“string”分配给类型“number”。 // Array<string> type NamesType = Array<string>; const students: NamesType = ['Maic', 'Tom'] // or 等价于 type NamesType2 = string[]; const students2: NamesType2 = ['Maic', 'Tom'];
定义数组对象的类型
// 例如一个数组 /** const arr = [{ name: 'Maic', age: 18, lovePlay: 'basketball' }]; **/ // 如何定义该数组内部的类型 type itemArr = { name: string; age: number; }; const arr: itemArr[] = [{ name: 'tick', age: 18 }]; console.log(arr[0].name);
类型推断和提示
定义对象类型
// Object type Attrs = Object; const personObj:Attrs = {}; // or type nameObj = { name: string; age: number } const personObj2: nameObj = { name: '大大', age: 18 } console.log(personObj2.age);
定义函数类型
type Fn = Function; const getAge:Fn = () => {}// const getAge: Function
函数形参类型
type NameType = string; function getName(name: NameType, age?:number) { return `我的名字是:${name}`; } // or function getName(name: string, age?:number): string { return `我的名字是:${name}`; } getName('Maic');
注意以上第二个形参中?age:number
代表这个形参可传可不传,并且这个函数返回的值类型是一个字符串
联合类型[string[] | number]
type idTypes = string[] | number;; const ids:idTypes = '123'; // or const ids2: string | number = 123; function getIds(id: idTypes, name?:string) { console.log(id.toString(),name); // console.log(id.join(',')); 如果不判断类型,则会直接提示 // 类型“idTypes”上不存在属性“join”。 // 类型“number”上不存在属性“join”。 if (Array.isArray(id)) { console.log(id.join(',')) } } getIds(['1','2'])
toString
这个方法在数组和number
中都有该方法,所以可以使用,如果某个方法只存在于一种类型中,则要类型收窄
判断该类型
interface 接口
interface
我们可以理解它是定义对象的一种类型,并且它具备扩展
对象属性,继承对象特征,在之前我们用type
定义了对象数据
type nameObj = { name: string; age: number } const personObj2: nameObj = { name: '大大', age: 18 };
用interface
定义对象
interface personObj2 { name: string; age: number; } const personObj4: personObj2 = { name: 'Maic', age: 18 }
如果我需要一个对象类型的属性是可选的
interface personObj2 { name: string; age?: number; }
只需要在定义的属性后面加个?
就行
extends 继承并扩展属性
// personObj5继承personObj2属性,所以personObj5具有personObj2的所有属性 interface personObj5 extends personObj2 {} interface personObj5 extends personObj2 { money: number } const personObj5: personObj5 = { name: 'Tom', money: 1000 }
type
通过交集扩展属性
/*** type 通过交集扩展属性 */ type personObj6 = personObj2 & { money: number} const personObj6:personObj6 = { name: 'Tom', money: 100 }
这里我们注意比较下type
与interfance
的区别
相同点
所有对象类型都可以用type
或者interface
来定义,type
在实际项目中更广义些,而interface
更多的时候描述一个对象类型更狭义一些,他们都可以定义对象类型
不同点
type
定义好了的数据,不能重载,且扩展属性需要使用交集扩展&
interface
可以重载,扩展属性需使用extends
type Animal = { name: string; } // 标识符“Animal”重复。ts(2300) // type定义完了的类型,不能重复定义 // type Animal = { // age: string; // } // & 扩展属性 type NewAnimal = Animal & {age: number}
interface Dog { name: string } interface Dog { age: number; } const dog:Dog = { name: '', age: 1 } interface childDog extends Dog { money: string }; const cDog:childDog = { name: 'xx', age: 0.5, money: '' }
as 类型断言
在项目中,如果你不知道该形参或者变量的类型,如果只是为了快点糊项目,不想被这个类型所拘束,那么你可以用as any
function $id(id) { return document.getElementById(id); } type elm = HTMLElement; const dom: elm = $id('app') as HTMLElement;
联合类型
type namesType = string | number; function getNames(name: namesType | 'Maic') { return name; } getNames('Maic'); // or getNames(123) function handlequest(url: string, methods: string, params: Object) { fetch(url); } const options = { url: 'https://www.baidu.com', methods: 'get', params: { q: 'test' } } as const; handlequest(options.url, options.methods as 'get', options.params);
in 收窄类型
interface shopList { js: string; node: string; } function printShop(books: shopList) { if ('js' in books) { console.log(`我买了 ${books['js']}`); } if ('node' in books) { console.log(`这是一本 ${books['node']}书籍`); } } printShop({ js: 'js设计模式', node: 'nodejs入门到放弃' });
将一个的enums
值的value
做为另一个对象的key
,将一个枚举值的key
作为一个对象的value
const enum FOODS { a = '鸭子', b = '鸡腿' } console.log(FOODS.a); type values = keyof typeof FOODS; // type values = "a" | "b" const foods: { [key in FOODS]: values } = { [FOODS.a]: 'a', [FOODS.b]: 'b' } /** * const foods: { 鸭子: "a" | "b"; 鸡腿: "a" | "b"; } */ console.log(foods[FOODS.a]);
instanceof 收窄
我们可以用instanceof
收窄数据类型
function transformParams(params) { if (params instanceof String) { console.log(params.toLocaleLowerCase()); } if (params instanceof Date) { console.log(params.toLocaleDateString()); } } transformParams('abc'); transformParams(new Date());
is 判断
我么判断一个形参是否在一个类型中
/** * is 判断参数类型 */ interface Fish { swim: Function } interface Bird { fly: Function } function isFish(arg: Fish | Bird): arg is Fish { return (arg as Fish).swim !== undefined } const isfish = isFish({swim: () => {}}); const isBird = isFish({fly: () => {}});
rest params
可以跟es6
一样...
扩展多个参数
/** * rest params */ function add(num: number, ...arg: number[]) { return arg.map((s) => s + num); } add(1, 2, 4, 5, 6); // [3,5,6,7] interface params { id: number; name: string; age: number; fav: string; } const curentParams: params = { id:1, name: 'Maic', age: 18, fav: 'play' } const {id, ...arg } = curentParams; /* const arg: { name: string; age: number; fav: string; } */
可选属性[?]or 只读属性[readonly]
我们想一个对象的属性可有可无,或者一个对象属性不能修改
/*** * * 对象属性修饰符 ? 可选 readonly 只读 */ interface params2 { readonly id: number; name: string; age?: number } const curentParams2: params2 = { id: 123, name: '' }; // age 可有可无 // curentParams2.id = 456; // 无法分配到 "id" ,因为它是只读属性。readonly id的属性不能修改
对象索引类型
通常我们一个对象的key
是字符串或者是索引,那么正确定义对象索引的类型就如下面
/** * 对象属性索引类型 */ interface params3 { [key: string]: string | number, [key: number]: number } const params3:params3 = { age: 18, 1: 123 }
如果我需要将一个对象key
声明成另一个对象的key
呢?那么我们可以使用[key in xxx]: string
enum LANGUAGE { ru = '俄罗斯', ch = '中国', usa = '美国' } type languKey = keyof typeof LANGUAGE; // type languKey = "ru" | "ch" | "usa" /** * const lang: { ru: string; ch: string; usa: string; } */ const lang: { [key in languKey]: string } = { ru: '1', ch: '2', usa: '3' }
交叉类型 x & b
/** * 交叉类型 */ interface span { color: string; } interface a { cursor: string; } type divType = span & a; const divStyle: divType = { color: '#111', cursor: 'pointer' }; console.log(divStyle.color, divStyle.cursor)
注意我们使用extends
也一样可以一样的效果
interface a { cursor: string; } // img 类型同时拥有cursor与{color: string}两个属性类型 interface img extends a { color: string } const imgStyle: img = { color: '#111', cursor: 'pointer' }
利用泛型复用 interface
通常在实际业务中, 通用的属性值可能类型不同那么我们会重复定义很多类型,比如下面
interface obj1 { a: boolean; } interface obj2 { a: string; } const obj1: obj1 = { a: true }; const obj2: obj2 = { a: '111' };
因此我们可以这么做
// 将多行类型合并成一个 interface objType<T> { a: T } const obj3: objType<boolean> = { a: false } const obj4: objType<string> = { a: 'hello' }
当我们看到interface objType<T> { a: T }
,我们怎么理解,首先objType
你可以把它看成一个接口名称,其实与普通申明一个普通接口名
一样,T
可以看成一个形参,一个占位符,我们可以在实际用的地方灵活的传入不同类型。
type 泛型复用
// Type泛型 interface obj2 { a: string; } type obj4Type<Type> = { content: Type; }; const obj5: obj4Type<obj2> = { content: { a: 'hello' } }; console.log(obj5.content.a); // hello
方法泛型复用
通常我们在项目中经常看到封装的工具函数中有泛型,那么我们可以简单的写个,具体可以看下下面简单的一个一个工具请求函数
/*** * * 方法泛型 */ function genterFeach<T>(url: string) { return { get: (params: T, config?) => { return fetch(url, { method: 'get', body: JSON.stringify(params), ...config }) }, post: (params: T, config?) => { return fetch(url, { method: 'post', body: JSON.stringify(params), ...config }) } } } interface paramsF { id: number, password: number, name: string } const useInfo = genterFeach<paramsF>('/v1/useInfo'); const login = genterFeach<paramsF>('/v1/login'); useInfo.get({id: 111, password: 12, name: 'Maic'}) login.post({id: 111, password: 12, name: 'Maic'})
readOnly 只读
/** * readonly */ type readData = readonly[string, number]; const data: readData = ['Maic', 18]; // data[1] = 20; 无法分配到 "1" ,因为它是只读属性 type readData2<T> = T; const data2: readData2<readonly string[]> = ['Maic']; // data2[0] = 'tom';// 类型“readonly string[]”中的索引签名仅允许读取 console.log(data2[0]);
[xx,xx,xx] as const
内部元素会变成一个常量,不可修改
const strArr = ['a', 'b', 3] as const; type strVal = typeof strArr; const strArr2: strVal = ['a', 'b', 3]; function getStrArr ([a, b, c]: [string, string, number]) { console.log(a,b,c) } // getStrArr(strArr);// 类型 "readonly ["a", "b", 3]" 为 "readonly",不能分配给可变类型 "[string, string, number]"。 function getStrArr2([a, b, c]: strVal) { console.log(a, b, c); } getStrArr2(strArr); // ok
泛型
对于泛型
在笔者初次遇见她时,还是相当陌生的,感觉这词很抽象,不好理解,光看别人写的,一堆泛型,或许增加了阅读代码的复杂度,但是泛型用好了,那么会极大的增加代码的复用度。当然,简单事情复杂化了,那么泛型也容易出错,代码也变得不易读。
我们写一个简单的例子来感受一下泛型
interface resopnseID { id: number } interface responseName { name: string } const responseId: resopnseID = { id: 123 } const responseName: responseName = { name: 'Maic' }
如果我想resopnseID
或者responseName
高度复用呢,如果有很多类似的字段,那么我是不是要写很多这种接口类型呢
interface keysType<T, V> { [key in T]: V } const responseId2: keysType<{id: number}, number>; const responseName2: keysType<{name: string}, string>; console.log(responseName2.age) // 类型“keysType<{ name: string; }, string>”上不存在属性“age”。 console.log(responseName2.name);
// 函数泛型 function setParamsType<T>(arg: T): T { return arg } console.log(setParamsType<string>('maic')); console.log(setParamsType<number>(18));
与下面等价,可以用interface
申明函数类型
// 接口泛型 interface paramsType<T> { [arg: T]: T } function setParamsType<T>(arg: T): T { return arg } const myParams: parsType<number> = setParamsType; // type 泛型 type parsType2<T> = { [arg: T]: T } const myParams2: parsType2<number> = setParamsType;
类泛型
我们在用class
申明类时,就可以约定类中成员属性的类型以及class
内部方法返回的类型
class Calculate<T> { initNum: T; max: string; add: (x: T, y:T) => T } const cal = new Calculate<number>(); cal.initNum = 0; cal.add = (x, y) => x+y; cal.add(1,2); // or const cal2 = new Calculate<string>(); cal.max = '123'; cal.add = (x, y) => x+y; cal.add(cal.max, '456') // 123456
约束泛型
在平时项目中我们使用泛型,我们会发现有时候,函数内部使用参数时,往往会提示属性不存在,比如
// 类型“T”上不存在属性“id”。 function getParams<T>(params: T) { if (params.id) { console.log('进行xxx操作') } } getParams({id: '123'})
此时我们就可以利用extends
约束泛型做到函数内部能正确访问
function getParams<T extends {id: string}>(params: T) { if (params.id) { console.log('进行xxx操作') } } getParams({id: '123'})
interface parmasType { id: string } function getParams2<T extends parmasType>(params: T) { if (params.id) { console.log('进行xxx操作') } }
接下来看一段原型属性推断与约束,我们可以看出构造函数与实例的关系
class Animal2 { name: string = 'animal'; } class Sleep { hour: number = 10; } class Bee extends Animal2 { age: number = 1; action: Sleep = new Sleep(); } function createInstance<T extends Animal2>(c: new () => T): T { return new c(); } console.log(createInstance(Bee).action.hour); // animal
keyof
我们对一个对象类型接口进行keyof
那么会返回对象属名组成的集合
interface keysObj { id: string; name: string, date: string | number, content: string } type keytype = keyof keysObj; // 等价于type keytype = 'id' | 'name' | 'date' | 'content' const objkey: keytype = 'content' // or const objkey2: keyof keysObj = 'id'; // 简写 // const objkey: keyof keysObj = 'content'
interface keysP { [key: number]: string } type keysType3 = keyof keysP; // type keysType3 = number const objkey3: keyof keysP = 1;
如何获取一个对象值的所有key
const objkey4 = { a: '111', b: '222', c: 333, d: 444 } type result = keyof typeof objkey4; // type result = "a" | "b" | "c" | "d" const objkey5:result = 'a'; // true
通过keyof
我们已经约束了一新值的所有值,那么它就再也不能赋值其他值了,比如
... const objkey5:result = 'e'; // error 不能将类型“"e"”分配给类型“"a" | "b" | "c" | "d"”
Extract
当我们对一个对一个泛型进行keyof
时,此时类型会变成string | number | symbol
三种类型,我们对变量进行赋值时,ts
会报错
function useKey<T, Key extends keyof T>(o: T, key: Key) { const keyName: string = key; // 不能将类型“string | number | symbol”分配给类型“string” console.log(o[keyName]); }
那么此时我们需要借助Extract
进一步进行约束
function useKey2<T, Key extends Extract<keyof T, string>>(o: T, key: Key) { const keyName: string = key; console.log(o[keyName]); }
注意我们看下ts
全局给我们提供的这个Extract
类型
type Extract<T, U> = T extends U ? T : never;
我们观察到Extract
就是约束了Key
的类型,那么我们也可以这么写,既然我知道Key
是字符串
function useKey3<T, Key extends string>(o: T, key: Key) { const keyName: string = key; console.log(o[keyName]); }
或者你也可以用或类型,指定keyName
可以是string | number | symbol
这三种类型
function useKey4<T, Key extends keyof T>(o: T, key: Key) { const keyName: string | number | symbol = key; console.log(o[keyName]); }
typeof
typeof
只能用于已经实际定义申明了的变量,返回该定义的变量的实际类型
let publicWebTech = '关注公众号:Web技术学苑'; type publicWeb = typeof publicWebTech; //type publicWeb = string const publicName: publicWeb = '';
但是注意如果用const
定义的变量,如果你keyof
一个常量,那么就会不一样了
const publicWebAuthor = 'Maic'; // or let publicWebAuthor = 'Maic' as const; type publicWebAuthor = typeof publicWebAuthor; const publicAuthor: publicWebAuthor = 'Maic';
获取一个对象
的所有属性类型
const objResult = {a: '11', b: '222'}; type objResultType = typeof objResult; /* type objResultType = { a: string; b: string; } */
获取一个函数
的返回类型,ReturnType
function fnTest() { return { a: '111', b: '222' } } type fntest = ReturnType<typeof fnTest> /** type fntest = { a: string; b: string; } **/
我们可以看下ReturnType
的类型定义
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
有时候我们定义一个枚举,我们想获取枚举的Key
,那么可以这么做
enum SERVER { TEST = 1, PRD = 2, DEV = 3, } type serverType = keyof typeof SERVER // type serverType = "TEST" | "PRD" | "DEV"
访问索引类型
有时我们需要访问具体接口的某个字段的类型或者数组中的类型
interface person { name: string; id: number, age: number } type nametype = person['age']; // type nametype = number type nameOrAge = person['age' | 'name']; // type nameOrAge = string | number type personKeys = person[keyof person]; // type personKeys = string | number
数组中的类型
const personArr = [ { name: 'Maic', age: 10 }, { name: 'tom', age: 18, id: 189 } ]; type items = typeof personArr[number]; /* type items = { name: string; age: number; id?: undefined; } | { name: string; age: number; id: number; } */
条件类型 extends
// 类型“"message"”无法用于索引类型“T”。 type messageOf<T> = T['message'];
此时可以用extends
type messageOf<T extends {message: string}> = T['message']
type isNumber<T> = T extends number ? number : string; const num: isNumber<string> = '123'; // const num: string
总结
1、在ts
定义基础数据类型,type
与interface
2、基础使用泛型,可以在接口
,函数
,type
使用泛型,泛型可以理解js
中的形参,更加抽象和组织代码
3、extends
约束泛型,并且可以在ts
中做条件判断
4、使用keyof
获取对象属性key
值,如果需要获取一个对象定义的key
,可以使用type keys = keyof typeof obj