typescript
一、什么是typescript
1.typescript的简介
ts = type + script,是script的超集,ts在js的基础上添加了类型支持
2.静态语言强类型和动态语言弱类型
①静态类型:编译期做类型检查
②动态类型:执行期做类型检查
③强类型:强制类型定义的语言,一旦某一个变量被定义类型,如果不经过强制转换,那么它永远就是该数据类型
// 表示a只能是数字类型
let a:number = 1
a = '1' // 会报错,因为a的类型已经被定义为数字类型
④弱类型:当某一个变量被定义类型,该变量可以不经过强制转换就能改变数据类型
let b = 1
b = '1' // 不报错,因为js是弱类型语言
js存在缺陷,js代码中绝大部分错误都是类型错误(Uncaught TypeError),而且js要运行起来才能在控制台上发现错误,对于程序员找bug很不方便,所以ts应运而生
二、typescript基础类型
1.js的基础类型
总所周知,js在es5时一共有两大类共六种数据类型:
①基础类型:number,string,boolean,null,undefined
②引用类型:object(arr和fn也是obj)(es新增了symbol)
2.ts基本类型写法
let 变量名:类型声明 = 值
let str: string = '123'
let num:number = 123
let nu:null = null
let un:undefined = undefined
let bo:boolean = true
// 推荐写法:基本类型不用注解
三、类型别名
// 最初的写法
const list : (string | number | boolean)[] = [1,'2',true]
// 类型别名就是将类型声明封装成一个变量方便去使用
// type 类型别名 = 类型声明
type MyType = (string | number | boolean)
const list1: MyType[] = [1, '2', true]
// 类型别名甚至可以将一整块类型声明全部封装
type MyArray = (string | number | boolean)[]
const list2: MyArray = [1, '2', true]
// 类型别名声明时变量名首字母推荐大写(为了更好地与js变量区别)
tips:类型别名就是类型声明的封装,方便代码可读与为维护
四、typescript的引用类型 -数组
1.基础数组
// 表示数字或字符串或布尔值数组
const list: (string | number | boolean)[] = [1, 2, 3, '4', true]
// 不推荐写法
const list2: Array<string | number | boolean> = [1, '2', true]
tips:|类似js的||运算符,代表或者的意思
2.元组
// 元组写法 const 数组名:[类型1,类型2...]
// 注意:元组中个数和类型都是限定死的,有两个类型就说明数组只能放两个元素,并且元素的类型与声明的类型要一一对应
const list1: [number, string] = [1, '2']
type MyArray = [number, boolean]
const list2: MyArray = [1, true]
五、typescript的引用类型 -函数
1.函数的分开指定和同时指定
// 分开指定 const 函数名 = (形参1:类型, 形参2:类型,...):返回值类型 => ...
const fn1 = (a: number, b: number): number => a + b
fn1(1, 2)
// 同时指定
type MyFn = (a: number, b: number) => number
const fn2: MyFn = (a, b) => a + b
tips:同时指定其实就是类型别名去指定函数类型,箭头后写的是返回值类型
2.函数的可选参数
// 可选参数:在想要可选的参数后加? 例如(a?:number) ***要写在必选参数后
const fn1 = (a: number, b?: number): number => 123
fn1(1) // 可以只指定一个参数
// 合并指定的可选参数写法与分开指定一致
type MyFn = (a: number, b?: number) => number
const fn2: MyFn = (a, b) => {
return a + b // 注意,这里会报错,因为b是可选参数,不能a+undefined,加个条件就不会报错了
}
3.函数返回值为空
// 在js中,若函数没有返回值默认为undefined
const fn1 = () => {}
console.log(typeof fn1()); // underfunded
// 在ts中,若函数没有返回值,建议指定返回值类型为void(空)
const fn2 = ():void => {}
console.log(typeof fn2()); // 但是注意,在js中返回的结果也是undefined(没有void这种数据类型)
type MyFn = () => void
// 若想返回值指定为undefined,需要在函数执行代码加个return(不推荐)
const fn3 = ():undefined => {return}
tips:当函数没有返回值时,而在类型声明时又不好直接声明undefined(因为需要在声明函数时不能省略return),所以void就是解决这个困境的
六、typescript的引用类型 -对象
1.对象类型声明的写法
// 初始写法 (与数组写法类似) const 对象名 : {属性名1:类型, 属性名2:类型...} = 对象
const zs : { name:string , age?:number } = {
name:'zs'
}
// 推荐写法 (利用类型别名封装对象声明类型)
type Person = { name:string, age?:number }
const ls:Person = {
name:'ls'
}
// 快速获取接口返回对象方式一 复制对象,粘贴,鼠标悬停
type objone = {
config_id: string;
start_time: number;
end_time: number;
app_id: number;
}
const obj: objone = {
config_id: "0",
start_time: 0,
end_time: 0,
app_id: 0,
}
// 快速获取接口返回对象方式二 装插件 -> crtl + shift + alt + v
interface RootObject {
config_id: string;
start_time: number;
end_time: number;
app_id: number;
}
// 对象里包含函数
type FnObj = {
name:string,
// 注意,这里是类型声明,原来对象里的函数的{}处是返回值类型的声明
sayHi(msg?:string):void
sayHello:(msg:string)=>void
}
const ww:FnObj = {
name:'ww',
sayHi(){},
sayHello:(msg)=>{}
}
2.接口
interface IPerson {
name:string,
sayHi(msg?:string):void,
// 在对象属性后加?表示可选属性
sayHello?:(msg:string) => void
}
// 同名的接口会合并
interface IPerson {
// 相同属性名类型要保持一致
name:string,
// 多出来的属性会被合并
age:number
}
const obj:IPerson = {
name:'zs',
age:18,
sayHi(){}
}
tips:type和interface区别
①type可以描述任意类型数据,interface只能描述对象
②type不能合并,interface可以合并
七、字面量类型和枚举
1.字面量类型
// 字面量类型就是将值作为类型,const声明的变量就是字面量类型
// 字面量类型不可改变,不可重新赋值
// 这时的a类型是'hello'类型,也就是值作为类型
const a:'hello' = 'hello'
// 字面量类型通常与联合声明一起配合,作用是提供代码选择
const fn1 = (name:'a'|'b'|'c'|'d'):void => {}
fn1("d") // 这时打fn1("")就会有代码选择
type MyFn = (age:'a'|'b'|'c'|'d') => void
const fn2:MyFn = (age) => {}
fn2("c")
2.枚举
// 枚举声明关键字为enum,和字面量用法类似,也是提供可选的值,不过是以对象.属性方式
// 写法规范:1.枚举的名称要大写字符开头或者全大写
// 2.键名尽量大写开头或全大写
// 3.键名要语义化
enum Gender {
// 与对象写法不同,这里是=
Man = 1,
Women = 0
}
const fn = (gender:Gender):void => {}
fn(Gender.Man)
fn(Gender.Women)
3.字面量类型和枚举的区别
// 枚举只能声明字符串和数字,不能声明其他类型;而字面量可以声明任意类型
// 枚举字符串不会自增,字符串枚举的每个成员必须有初始值
enum TextNumber {
One, // 这样写法默认从0开始自增
Two
}
enum TextNumber1 {
One = 100, // 这样写法默认从100开始自增
Two
}
// 枚举可以合并,但是属性名不能重复
enum TextNumber {
three = 3
}
const fn = (number:TextNumber):void => {}
fn(TextNumber.three)
console.log(TextNumber.three); // 枚举可以对象.属性来取值,但是字面量不行
tips: 字面量类型比枚举所用的范围更广泛,工作中更推荐用字面量类型
八、as断言和any
1.as断言
// as 值类型 放在值后面去断言,表示这个值我说就是这个类型
const fn = (a, b) => {
return a + b as number
}
fn(1,2) // 这里鼠标悬停就显示的是数字类型了
2.any
// any在ts里表示的是任意类型,例如
const fn = (a:any):any => a
这样就表示这个函数参数和返回值可以是任意类型
any使用到的场景是在工作中跳过某个需求时使用,一般的情况下少用any,因为使用any就失去了强类型保护,相当于写js
九、typeof
typeof在ts里用于获取值的类型
// 数组
const list: number[] = []
// 这里就把list的类型提取出来了(数字数组)
const list1: typeof list = []
// 对象
interface MyObj {
name: string,
age?: number
}
// 初始写法
const obj: MyObj = {
name: 'zs'
}
// 效果和上面代码相同
const obj1: typeof obj = {
name: 'ls'
}
// 函数
type MyFn = (a: number, b: number) => number
const fn: MyFn = (a, b) => a + b
// typeof不能直接获取函数的返回值类型
const fn1: typeof fn = (a, b) => a + b
// 获取返回值正确写法(要先拿到返回值再去获取这个返回值的类型)
const res = fn(1, 2)
const fn2 = (a, b): typeof res => a + b
十、泛型函数
1.分开指定
// 泛型的写法 const 函数名 = <自定义变量名>(a:自定义变量名)=>返回值
// 泛型可以通过传入参数的数据类型来推断出返回值的数据类型,方便复用
const fn = <T> (a:T) => a
此时fn会根据所传参数类型而决定返回值类型
2.同时指定
type MyFn = <T>(a:T) => T
const fn: MyFn = (a) => a
tips:泛型函数完整写法
// 泛型完整写法(当你写着写着代码报错时就用这个写法)
type MyObj = {
name: string,
age?: number
}
const fn = <T> (a:T) => a
// 不加<MyObj>的话,类型就会少了age参数
const obj = fn<MyObj>({ name: 'zs' })
3.使用泛型指定更详细的参数类型
// 动态获取参数类型
// 对象
const objFn = <T>(a: T): { a: T } => ({a})
// 此时res1是对象里包着数字类型的a
const res1 = objFn(1)
// 数组
const arrFn = <T>(a: T): T[] => [a]
// 此时res2是字符串数组
const res2 = arrFn('1')
4.使用泛型指参数里元素的类型
// 获取对象参数里属性值的类型
const objFn = <T>(obj: { name: T }): T => { return obj.name }
const res1 = objFn({ name: 'zs' }) // string
const res2 = objFn({ name: 1 }) // number
// 获取数组里元素的类型
const arrFn = <T>(arr: T[], index: number): T => arr[index]
const res3 = arrFn([1], 0) // number
const res4 = arrFn(['1'], 0) // string
十一、泛型约束
单一的参数类型范围太广了,当我们进行某种需求的传参时,我们会发现类型有点不好指定,这个时候就需要泛型约束
1.语法
type MyLength = {
length:number
}
// 泛型约束 extends 相当于且而不是继承,意思为 要传入参数并且要具有length属性
// type也可以作为泛型约束(因为extens意思是且不是继承)
const arrFn = <T extends Mylength>(a:T): T => a
// '123'字符串具有length属性,所以可以传入
const res1 = arrFn1('123')
接口的写法和类型别名一致
2.给泛型添加更详细的约束
type MyLength = {
length:number
}
// 必须传入数组,并且数组里的每项元素都具有长度属性
const arrFn3 = <T extends MyLength>(arr: T[]): T => arr[0]
const res3 = arrFn3(['1']) // string(返回值类型是T,也就是传入参数里元素的类型)
十二、泛型函数(多个变量)
泛型函数可以指定多个变量的类型
1.语法
// 使用泛型可以指定多个变量,<>里的自定义变量顺序不影响结果
// 泛型变量不能进行算术运算
// 指定对象属性
const fn1 = <T, K>(a: T, b: K) => ({ a, b })
// 指定数组元素
const fn2 = <T, K>(a: T, b: K) => [a, b]
const res1 = fn1(1, '3') // {a:number,b:string}
const res2 = fn2(true, 1) // boolean|number[]
2.给多个变量添加约束
// 利用泛型约束和keyof可以实现变量条件的指定约束
// K extends keyof T 表示K既要是一个泛型,并且是存在于T里面的属性
const fn = <T, K extends keyof T>(obj: T, key: K) => obj[key]
const res = fn({ name: 'zs', age: 18 }, 'age')
十三、keyof
// keyof 用于遍历对象内的属性名用于提供代码提示
// keyof通常和typeof一起使用,因为keyof后面跟的是类型,typeof用于提取值类型
const obj = {
name:'zs',
age:18,
gender:1,
hobbit:'none'
}
const objFn = (key:keyof typeof obj) => {return obj[key]}
// 此时res1是一个联合属性 string|number
const res1 = objFn('age')
十四、泛型接口
// 泛型接口可以处理一些大部分属性类型相同,少部分属性类型不同的对象数据,和泛型函数不一样的是,泛型函数的<>写在函数名前,而泛型接口的<>写在接口名后
interface IPhone<T> {
version: string
age: T
publish: (msg: T) => void
}
// 这里就可以指定age和msg的类型
const obj: IPhone<string> = {
version: '1.3.4',
age: '18',
publish(msg) { }
}
十五、泛型工具
1.Partial和Readonly
使用泛型工具Partial可以将类型里所有参数全部设置为可选参数
interface MyObj {
name?: string
age: number
gender: number
shy: string
cat: number
}
// 此时MyObj里的接口参数全是可选的
const obj: Partial<MyObj> = {
shy: '1'
}
readonly可以将某个属性设置为只读(只能读取,不能修改),也可以将整个对象设置为只读
// 设置单个
interface MyRead {
// 在需要设置只读的属性名前加readonly
readonly url: string
method: string
}
const obj1: MyRead = {
url: '123',
method: 'get'
}
// 设置整个
const obj2: Readonly<MyRead> = {
url: '4399',
method: 'post'
}
设置过只读后的属性不能修改
2.pick和omit
interface MyObj {
name: string
age: number
gender: number
}
// pick用处是将类型中所需要的属性提取出来(对象里只需要写提取出来的参数)
const obj1: Pick<MyObj, 'name' | 'age'> = {
name: 'zs',
age: 18
}
// omit用处是将类型中不想要的属性删除(删除的属性就不用写进对象里)
const obj2: Omit<MyObj, 'name'> = {
age: 18,
gender: 1
}
3.ReturnType
// 获取函数的返回值类型可以使用ReturnType
const fn = () => ({name:'zs'})
// ReturnType常常和typeof一起使用,因为<>里必须是类型而不是值
const res:ReturnType<typeof fn> = {
name:'ls'
}
// 不想用typeof也可以自己声明类型
type MyFn = () => {name:string}
const res1:ReturnType<MyFn> = {
name:'zs'
}
十六、索引签名类型
// [key: string]: any 意思是对象可以添加任意个属性,属性值可以是任意类型
// (推荐使用any,使用泛型的话会有类型冲突)
interface MyObj {
name: string
// 这个key可以自定义
[key: string]: any
}
const obj: MyObj = {
name: 'zs'
}
// 可以在对象里新增任意属性,并且属性值可以是任何类型
obj.sragafasdda = 1
十七、映射类型
interface MyObj {
name:string
age:number
gender:number
}
// 可以根据类型进行遍历生成
// 注意:映射类型只能在类型别名中使用,不能在接口中使用。
type MyMap = {
// 循环生成MyObj里的类型,key in 配合keyof来使用,因为keyof可以用于遍历对象内的属性名
[key in keyof MyObj]:number
}
十八、索引访问类型
interface IPhone {
name: string
age: number
}
// 使用类型["属性名"]可以访问里面属性的类型
const age: IPhone["age"] = 0 // age是number类型
// 使用keyof 可以遍历里面所有属性的类型,变成一个联合类型
const age1: IPhone[keyof IPhone] = '12' // age1是联合类型,number|string
tips:使用映射和访问实现Partial和Readonly的效果
type Person = {
name: string
age: number
gender: number
}
// 实现Partial原理(全部设为可选参数)
type MyPartial<T> = {
// 遍历属性名通过映射去实现,判断该属性是什么类型通过 索引访问 实现
[P in keyof T]?: T[P]
}
const obj1: MyPartial<Person> = {
name: 'zs',
}
// 实现ReadOnly原理(全部设为只读)
type MyReadOnly<T> = {
readonly [R in keyof T]: T[R]
}
const obj2: MyReadOnly<Person> = {
name: 'zs',
age: 18,
gender: 1
}