最近半年项目升级 ts,一边看文档,一边实践,ts
基础语法非常简单,但是写好ts
就非常不简单,typescript
严格来讲算是一门强类型语言,它赋予js
类型体系,让开发者写js
更加严谨,并且它具备强大的类型推断,并且能在node
和浏览器
中运行。对于项目而言,使用 typescipt 对提升项目的规范与严谨性更加友好。
本文只做笔者项目中常遇到的一些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
5、有一篇笔者很早之前的一篇ts 笔记
6、本文示例code-example
更多学习ts
查看TS 官方文档,也可以看对应翻译中文版https://yayujs.com/