一、基础数据类型
JS 的八种内置类型:
string
、number
、boolean
、null
、undefined
、object
、bigint
、symbol
注意: 1.默认情况下 null 和 undefined 是所有类型的子类型,即 null 和 undefined 可以赋值给其他类型 2.在严格模式下(tsconfig.json 指定了"strictNullChecks":true),null 和 undefined 只能赋值给 void、undefined、null
let str: string = 'test'
str = null
str = undefined
let num: number = 666
num = null
num = undefined
let obj: object = {}
obj = null
obj = undefined
let sym: symbol = Symbol('me')
sym = null
sym = undefined
let bool: boolean = false
bool = null
bool = undefined
let big: bigint = 100n
big = null
big = undefined
注意: 1.虽然 number 和 bigint 都表示数字,但是两个类型不兼容
let num1: number = 9
let big1: bigint = 100n
// big1 = num1 // 不能将类型“number”分配给类型“bigint” ts(2322)
// num1 = big1 // 不能将类型“bigint”分配给类型“number” ts(2322)
二、其他类型
Array 数组
- 对数组类型定义的方式
let arr: string[] = []
let arr1: string[] = ['1', '2']
let arr2: Array<string> = []
let arr3: Array<string> = ['1', '2']
- 定义联合类型数组
// 表示arr4这个数组存储的值可以是“number”和“string”
let arr4: (number | string)[] = [1, 2, 3, 'a', 'this']
- 定义指定对象成员的数组(对象数组)
interface Arrobj {
name: string
age: number
}
let arr5: Arrobj[] = [{ name: 'jimmy', age: 22 }]
// let arr6: Arrobj[] = [{ name: 'jimmy' }] 类型 "{ name: string; }" 中缺少属性 "age",但类型 "Arrobj" 中需要该属性 ts(2741)
// let arr7: Arrobj[] = [{ name: 'test',age: 18 , class: 18}] 对象字面量只能指定已知属性,并且“class”不在类型“Arrobj”中 ts(2353)
Function 函数
- 函数声明
function sum(x: number, y: number): number {
return x + y
}
- 函数表达式
let sum1: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y
}
- 用接口定义函数类型
用接口定义函数类型时,对等号左侧进行类型限制,可以保证对函数名赋值时参数个数、参数类型、返回值类型不变
interface SumFunc {
(x: number, y: number): number
}
let sum2: SumFunc = function (x: number, y: number): number {
return x + y
}
- 可选参数
注意:可选参数后面不允许再出现必需参数
function sum3(x: number, y?: number) {
y = y || 0
return x + y
}
let val4 = sum3(1, 3)
let val3 = sum3(3)
console.log(val4) // 4
console.log(val3) // 3
- 参数默认值
function sum4(x: number, y: number = 0) {
return x + y
}
let val5 = sum4(5)
let val6 = sum4(3, 3)
console.log(val5) // 5
console.log(val6) // 6
- 剩余参数
function sum5(x: number, y: number, ...nums: number[]) {
const list = [x, y, ...nums]
return list.reduce((total, current, index) => total + current, 0)
}
const resultVal = sum5(5, 5, 1, 1, 1, 1, 1)
console.log(resultVal) // 15
- 函数重载
// 使用不同类型的参数调用同一个函数,函数根据不同的参数返回不同类型的结果
function add(x: number | string, y: number | string) {
if (typeof x === 'string' || typeof y === 'string') {
return x.toString() + y.toString()
}
return x + y
}
console.log(add(1, 2)) // 3
console.log(add('1', '2')) // 12
const result = add('this', ' is')
// 为什么会出现以下这种情况呢?因为根据传递的参数不同,add函数可能出现的返回值类型string\number,number类型没有split方法
// result.split(' ') 类型“number”上不存在属性“split”
// 使用函数重载解决当前问题:
// 函数重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。
function add1(x: number, y: number): number
function add1(x: string, y: string): string
function add1(x: string, y: number): string
function add1(x: number, y: string): string
function add1(x: number | string, y: number | string) {
if (typeof x === 'string' || typeof y === 'string') {
return x.toString() + y.toString()
}
return x + y
}
const result1 = add1('this', ' is')
console.log(result1.split(' ')) // ['this', 'is']
元组 Tuple
特性:限制数组元素的个数与类型,即类型和个数必须一致
用途:保存定长定数据类型的数据
注意:元组类型只能用于表示已知元素数量和类型的数组
let a: [string, number]
a = ['this', 8]
// a = ['this',9,0] error
// a = [9,'this'] error
- 元组类型的解构赋值
let t1: [number, string] = [1, 'this']
let [id, username] = t1
console.log(`id:${id}`) // id:1
console.log(`username:${username}`) // username:this
// let [numVal, strVal, other] = t1 长度为 "2" 的元组类型 "[number, string]" 在索引 "2" 处没有元素 ts(2493)
- 元组类型的可选元素
let optionalTuple: [string, number?]
optionalTuple = ['this', 10086]
console.log(`optionalTuple:${optionalTuple}`) // optionalTuple:this,10086
optionalTuple = ['what']
console.log(`optionalTuple:${optionalTuple}`) // optionalTuple:what
- 元组类型的剩余元素
注意:元组类型的最后一个元素可以是剩余元素
let restTuple: [number, ...string[]] = [10086, 'this', 'what', 'red']
console.log(restTuple[0])
console.log(restTuple[1])
- 只读的元组类型
// 关键字:readonly
const point: readonly [number, number] = [10, 20]
// point[1] = 10086 无法为“1”赋值,因为它是只读属性 ts(2540)
void
定义:表示没有任何类型,和其他类型是平等关系,不能直接赋值
注意:
- 一般不用 void 声明变量,只在函数没有返回值时去声明
- 函数没有返回值将得到 undefined,如果把返回值类型声明为 undefined 会报错,而是使用 void 声明类型
- 你只能为 void 类型赋予 null、undefined、void 类型(在 strictNullChecks 未指定为 true 时)
let v1: void
// let v2:number = v1 不能将类型“void”分配给类型“number” ts(2322)
// v1 = null
v1 = undefined
let v3: void = v1
function fun1(): void {
console.log('this is function')
}
fun1()
never
定义:表示哪些不存在的值的类型
值不存在的两种情况:
- 函数执行过程中抛出异常(抛出异常会直接中断程序运行,使程序运行不到返回值的那一步)
- 函数陷入死循环,使程序永远无法运行到函数返回值的那一步,永不会返回
注意:- never 类型与 null、undefined 一样,是任何类型的子类型,可以赋值给任何类型
- 但是没有任何类型是 never 的子类型,即没有类型可以赋值给 never 类型(never 除外),即使 any 也不可以赋值给 never
作用:
- 使用 never 避免出现新增了联合类型但没有对应的实现,目的就是写出类型绝对安全的代码
// 异常
function err(msg: string): never {
throw new Error(msg)
}
// 死循环
function loopForever(msg: string): never {
while (true) {}
}
let ne: never
let nev: never
let an: any
// ne = 1 error
// ne = false error
// ne = 'this' error
// nev = an error
// nev = ne ok
/** ok
ne = (() => {
throw new Error('异常')
})()
ne = (() => {
while (true) {}
})()
*/
// 利用never类型的特性实现全面性检查:
function check(foo: string | number) {
if (typeof foo === 'string') {
// foo被收窄为 string 类型
} else if (typeof foo === 'number') {
// foo被收窄为 number 类型
} else {
// foo 在这里为 never
const result: never = foo
}
}
any
任何类型都可以被归为 any 类型,any 类型是类型系统中的顶级类型
- any 类型可以被赋值为任意类型,any 可以赋值给任何类型
let a1: any = 10086
a1 = 'this'
a1 = false
a1 = undefined
a1 = null
a1 = []
a1 = {}
let a2: string = 'this'
a2 = a1
- any 可以访问任何属性和方法(执行时会报错)
// let any1: any = 'hello world'
// console.log(any1.username)
// console.log(any1.student.class)
// any1.setName('Tom')
// any1.setName('Tom').go()
// any1.username.setFirstName('Join')
- 变量在声明时未指定类型,那么系统会自动识别其为任意值类型(any)
let something // 等同于 let something:any
something = 'this is hhh'
something = 10086
// something.setName('Tom')
unknown
定义:unknown 与 any 一样,所有类型都可以分配给 unknown
unknown 与 any 的区别:
- 任何类型的值都可以赋值给 any,同时 any 类型的值也可以赋值给任何类型
- 任何类型的值都可以赋值给 unknown,但 unknown 只能赋值给 unknown 和 any
let notSure: unknown = 4
notSure = 'this is phone'
notSure = false
let testNum: number = 10086
// testNum = notSure 不能将类型“unknown”分配给类型“number” ts(2322)
// 1.如果不缩小类型,就无法对unknown类型执行任何操作
function getDog() {
return '10086'
}
const dog: unknown = { hello: getDog }
// dog.hello() 类型“unknown”上不存在属性“hello”。ts(2339)
- 如果不缩小类型,就无法对 unknown 类型执行任何操作
function getDog() {
return '10086'
}
const dog: unknown = { hello: getDog }
// dog.hello() 类型“unknown”上不存在属性“hello”。ts(2339)
- 缩小类型
function getDogName() {
let x: unknown
return x
}
const dogName = getDogName()
// const tom = dogName.toLowerCase() 类型“unknown”上不存在属性“toLowerCase” ts(2339)
// 第一种方式:
if (typeof dogName === 'string') {
// const tom = dogName.toLowerCase() // ok
}
// 第二种方式:
// 类型断言
// const tom = (dogName as string).toLowerCase() // ok
Number\String\Boolean\Symbol - 包装对象,对象类型
原始类型:number、string、boolean、symbol、null、bigint、undefined
对象类型:Number、String、Boolean、Symbol
注意:
- 原始类型兼容对应的对象类型,但对象类型不兼容对应的原始类型
- 可以把原始类型数据赋值给对象类型,但不能把对象类型数据赋值给原始类型
- 不要使用对象类型注解值的类型,因为没有意义
let stri: string
let Stri: String
// Stri = stri
// stri = Stri 不能将类型“String”分配给类型“string”
object、Object、{}
object:小 object,代表所有非原始类型,即不能把 number、string、boolean、symbol 等原始类型赋值给 object;严格模式下,null 和 undefined 类型也不能赋值给 object
Object:大 Object,代表所有拥有 toString、hasOwnProperty 方法的类型,所有原始类型、非原始类型都可以赋值给 Object;严格模式下,null 和 undefined 类型也不能赋值给 Object
{}:空对象类型,和大 Object 一样,表示原始类型和非原始类型的集合;严格模式下,null 和 undefined 类型也不能赋值给{}
let lowerObject: object
let Stri1: String
// lowerObject = Stri1 // 对象类型(非原始类型)可以赋值给小object ok
// lowerObject = 1 // error
// lowerObject ='this' // error
// lowerObject = false // error
// lowerObject = null // 严格模式下不行
// lowerObject = undefined // 严格模式下不行
lowerObject = {} // ok
let upperObject: Object
upperObject = 1 // ok
upperObject = 'this' // ok
upperObject = false // ok
// upperObject = undefined // 严格模式下不行
// upperObject = null // 严格模式下不行
upperObject = {} // ok
lowerObject = upperObject // ok
upperObject = lowerObject // ok
三、类型推断
定义:基于赋值表达式推断类型的能力称为“类型推断”
作用:
- 具有初始化的变量、有默认值的函数参数,函数返回的类型都可以根据上下文推断出来
- 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断为 any 类型而完全不被类型检查
let aStr = 'this is string' // 自动推断出aStr变量的类型为string
// aStr = 1 不能将类型“number”分配给类型“string” ts(2322)
// aStr = false 不能将类型“boolean”分配给类型“string” ts(2322)
function aFunc1(a: number, b: number) {
return a + b
}
const aNum1 = aFunc1(1, 2) // 推断出 aNum1 的类型为 number
function aFunc2(a: number, b = 1) {
return a + b
}
const aNum2 = aFunc2(2) // 推断出 aNum2 的类型为 number
// const aNum3 = aFunc2(1,'2') // 第二个参数报错:类型“string”的参数不能赋给类型“number”的参数
let aAny1
aAny1 = 'this is'
aAny1 = 10086
四、类型断言
- 语法
// 尖括号:
let someVal: any = 'this is a string'
let strLength: number = (<string>someVal).length
// as 语法 (推荐)
let someVal1: any = 'this is a string'
let strLength1: number = (someVal1 as string).length
- 非空断言
后缀表达式操作符 “!”,用于断言操作对象是非 null 和 undefined 类型
// x! 将从 x 值域中排除null和undefined
let test1: null | undefined | string
// console.log(test1!.length) // ok
// console.log(test1.length) // test1可能为 “null” 或“未定义” ts(18049)
- 确定赋值断言
在实例属性和变量声明后放置一个“!”,从而告诉 ts 该属性会被明确赋值
let test2: number
init1()
// console.log(2 * test2) 在赋值前使用了变量“test2”。ts(2454)
function init1() {
test2 = 10
}
let test3!: number // 非空断言
init2()
console.log(2 * test3) // 20
function init2() {
test3 = 10
}
五、字面量类型
字面量不仅可以表示值,还可以表示类型
字符串字面量类型、数字字面量类型、布尔字面量类型
注意:
- 字符串字面量类型是 string 类型的子类型,而 string 类型不一定是 字符串字面量类型;其他的同理
let bTest: 'this is a string' = 'this is a string'
let bTest1: 1 = 1
let bTest2: true = true
// bTest = 'what' 不能将类型“"what"”分配给类型“"this is a string"” ts(2322)
// bTest1 = 2 不能将类型“2”分配给类型“1” ts(2322)
// bTest2 = false 不能将类型“false”分配给类型“true” ts(2322)
let bTest3: string = 'a apply'
// bTest = bTest3 不能将类型“string”分配给类型“"this is a string"” ts(2322)
bTest3 = bTest
- 字符串字面量类型
// 定义单个字面量类型没有太大的用处,一般用于把多个字面量类型组合成一个联合类型
type Direction = 'up' | 'down'
function move(dir: Direction) {
console.log(dir)
}
move('up') // ok
// move('left') 类型“"left"”的参数不能赋给类型“Direction”的参数 ts(2345)
- 数字字面量类型及布尔字面量类型
interface Config {
size: 'small' | 'big'
isEnable: true | false
margin: 0 | 2 | 4
}
- let 和 const 分析
const 定义一个不可变的常量,在缺省类型注解的情况下,类型推断会推断出常量的类型为对应的“字面量类型”
let 定义一个变量,在缺省类型注解的情况下,类型推断会推断出变量的类型为对应的字面量类型的“父类型”
// const 定义一个不可变的常量,在缺省类型注解的情况下,类型推断会推断出常量的类型为对应的“字面量类型”
const bStr = 'this is string' // const bStr: "this is string" -- 字面量类型
const bNum = 1 // const bNum: 1 -- 字面量类型
const bBool = true // const bBool: true -- 字面量类型
// let 定义一个变量,在缺省类型注解的情况下,类型推断会推断出变量的类型为对应的字面量类型的“父类型”
let bStr1 = 'this is string' // let bStr1: string
let bNum1 = 2 // let bNum1: number
let bBool1 = true // let bBool1: boolean
六、类型拓宽
通过 let 或 val 定义的变量、函数的形参、对象的非只读属性,如果满足指定了初始值且未显式添加类型注解的条件,那么它们推断出来的类型就是指定的初始值字面量类型拓宽后的类型,这就是字面量类型拓宽
即字面量类型的父类型
let cStr = 'this is string' // 类型: string
let cStrFunc = (str = 'this is string') => str // 类型: (str?:string) => string
const constStr = 'this is string' //类型: thi is string
let cStr2 = constStr // 类型:string
let cStrFunc2 = (str = constStr) => str // 类型:(str?:string) => string
- 显式类型注解控制类型拓宽行为
const constStr1: 'this is string' = 'this is string'
let cStr3 = constStr1 // 类型:this is string
- 对 null 和 undefined 的类型进行拓宽
// 通过let、var定义的变量如果满足未显式什么类型注解且被赋予了null和undefined值,则会推断出变量的类型为any
let cX = null // 类型拓宽成any
let cY = undefined // 类型拓宽成any
const cZ = null // 类型推断null
let anyFun = (param = null) => param // 形参的类型式null
let cZ2 = cZ // 类型 null
let cX2 = cX // 类型 null
let cY2 = cY // 类型 undefined
// 例子:
interface Vector3 {
x: number
y: number
z: number
}
function getComponent(vector: Vector3, axis: 'x' | 'y' | 'z') {
return vector[axis]
}
let axis = 'x' // 类型推断出 axis的类型为string
let vec = { x: 10, y: 20, z: 30 }
// getComponent(vec,axis) 类型“string”的参数不能赋给类型“"x" | "y" | "z"”的参数 ts(2345)
const axis2 = 'x' // 类型推断出 axis2的类型为 x
getComponent(vec, axis2)
// 对于对象,ts的拓宽算法会将其内部属性视为将其赋值给let关键字声明的变量,进而推断其属性的类型
// 即 let x = 1 ===> x:number
const cObj = {
// 类型 const cObj: { x: number }
x: 1,
}
cObj.x = 10086
// cObj.y = 9999 类型“{ x: number; }”上不存在属性“y” ts(2339)
// cObj.name = 'tom' 类型“{ x: number; }”上不存在属性“name” ts(2339)
- 使用"显式类型"注释覆盖 ts 的默认行为
const cObj1: { x: 1 | 3 | 5 } = {
x: 1,
}
- 使用“const 断言”覆盖 ts 的默认行为
不要将 const 断言 与 let 和 const 混淆,后者在值空间中引入符号,前者是一个纯粹的类型级构造
使用 const 断言 时,ts 会推断出最窄的类型,没有拓宽
// 类型:const cObj2: { x: number; y: number; }
const cObj2 = {
x: 1,
y: 2,
}
// 类型:const cObj3: { x: 1; y: number; }
const cObj3 = {
x: 1 as const,
y: 2,
}
// 类型:const cObj4: { readonly x: 1; readonly y: 2; }
const cObj4 = {
x: 1,
y: 2,
} as const
// 类型:const cArr1: number[]
const cArr1 = [1, 2, 3]
// 类型:const cArr2: readonly [1, 2, 3]
const cArr2 = [1, 2, 3] as const
// cArr2.push(4) 类型“readonly [1, 2, 3]”上不存在属性“push” ts(2339)
// 类型:const cArr3: (string | number)[]
const cArr3 = [1, 2, 'a']
七、类型缩小
通过某些操作将变量的类型由一个较为宽泛的集合缩小到相对较小、较明确的集合
let dFunc = (anyThing: any) => {
if (typeof anyThing === 'string') {
return anyThing // 类型为string
} else if (typeof anyThing === 'number') {
return anyThing // 类型为number
}
return null
}
let dFunc2 = (anyThing: string | number) => {
if (typeof anyThing === 'string') {
return anyThing // 类型为string
} else if (typeof anyThing === 'number') {
return anyThing // 类型为number
}
}
// 以下从联合类型中排除null的方式是错误的:typeof null 的结果是 object
const el = document.getElementById('app') // 类型:HTMLElement | null
if (typeof el === 'object') {
console.log(el) // el的类型:HTMLElement | null
}
// 以下排除false的方式是错误的:空字符串、0都属于false值
function dFunc3(x?: number | string | null) {
if (!x) {
x // 类型:string | null | number | undefined
}
}
// 缩小类型的方法 -- 明确标签,这种模式也被称为”标签联合“ 或 ”可辨识联合“
interface UploadEvent {
type: 'upload'
filename: string
content: string
}
interface DownloadEvent {
type: 'download'
filename: string
}
type AppEvent = UploadEvent | DownloadEvent
function handleEvent(e: AppEvent) {
switch (e.type) {
case 'download':
console.log(e) // 类型:DownloadEvent
break
case 'upload':
console.log(e) // 类型:UploadEvent
break
}
}
八、联合类型
表示取值可以为多种类型中的一种,使用 | 分隔每个类型
let eFav: string | number
eFav = 'three'
eFav = 10086
// 联合类型通常与null 或 undefined 一起使用
const sayHello = (name: string | undefined) => {
console.log('Hello ' + name)
}
sayHello('join')
sayHello(undefined)
// 字面量类型 联合类型
let eFav2: 1 | 2 = 1
type EventNames = 'click' | 'scroll' | 'mousemove'
九、类型别名
给类型起一个新名字;常用于联合类型
注意:类型别名,仅仅只是给类型取了一个新名字,并不是创建了一个新的类型
type Message = string | string[]
let greet = (message: Message) => {
console.log(message)
}
greet('这是一条消息')
greet(['this is string', 'one message'])
十、交叉类型
将多个类型合并为一个类型,即将多种类型叠加在一起成为一种类型,它包含了所需的所有类型的特性,使用 & 定义交叉类型
注意:
- 将原始类型、字面量类型、函数类型等原子类型合并成为交叉类型,是没有任何用处的
- 交叉类型一般用于将多个接口类型合并成一个类型,从而实现等同接口继承的效果,即合并接口类型
type Useless = string & number // Useless 的类型是 never
// let aNev: Useless = 1 不能将类型“number”分配给类型“never” ts(2322)
type IntersectionType = { id: number; name: string } & { age: number }
// 等价于 => type IntersectionType = { id: number; name: string; age: number }
const mixed: IntersectionType = {
id: 1,
name: 'biubiu',
age: 18,
}
- 如果合并的多个接口类型存在同名属性会是什么效果呢?
type IntersectionTypeConfict = { id: number; name: string } & { age: number; name: number }
// 属性name的类型是 string 和 number 合并之后的类型(string 和 number类型不兼容),即never
// 等同于 => type IntersectionTypeConfict = { id: number; name: never; age: number; }
// const mixedConfict: IntersectionTypeConfict = {
// id: 1,
// name: 'biubiu', // 不能将类型“string”分配给类型“never” ts(2322)
// age: 18,
// }
- 如果同名属性的类型兼容会有什么效果呢?
// 如果一个是number、另一个是number的子类型、数字字面量类型,合并后name属性的类型就是两者中的"子类型"
type IntersectionTypeConfict1 = { id: number; name: 2 } & { age: number; name: number }
// const mixedConfict2: IntersectionTypeConfict1 = {
// id:1,
// name:10086, // 不能将类型“10086”分配给类型“2” ts(2322)
// age: 2
// }
- 如果同名属性是非基本数据类型的话会有什么效果呢?
interface A {
x: {
d: true
}
}
interface B {
x: {
e: string
f: 10
}
}
interface C {
x: {
f: number
}
}
type ABC = A & B & C
let abc: ABC = {
x: {
d: true,
e: '',
// f: 666, 不能将类型“666”分配给类型“10” ts(2322)
f: 10, // ok
},
}
console.log(abc) // { x: { d: true, e: '', f: 10 } }
十一、接口 interface
使用接口 interface 来定义对象的类型
什么是接口?
- 在面向对象语言中,接口 interface 是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现
- ts 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对对象的形状进行描述
注意:
- 接口的首字母进行大写
- 定义的变量必须与接口一致,多与少都是不行的
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 0,
}
// 类型 "{ name: string; }" 中缺少属性 "age",但类型 "Person" 中需要该属性 ts(2741)
// let bob:Person = {
// name:'Bob'
// }
- 可选与只读属性
**可选:**该属性可以声明也可以不声明,如果声明,类型要保持一致
**只读属性:**用于限制属性只能在对象刚刚创建的时候修改其值
interface Person2 {
readonly name: string // 只读属性
age?: number // 可选
}
- ReadonlyArray类型
ReadonlyArray类型与 Array,前者把所有可变方法去掉了,确保数组创建后不能被修改
let aArr: number[] = [1, 2, 3, 4]
let ro: ReadonlyArray<number> = [0, 1, 2, 3, 4]
// ro[0] = 12 // error
// ro.push(5) // error
// ro.length = 100 // error
// aArr = ro 类型 "readonly number[]" 为 "readonly",不能分配给可变类型 "number[]" ts(4104)
- 任意属性
利用索引签名的形式声明任意属性
注意:
一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
即:
如果任意属性的类型是 string,name 确定属性和可选属性的类型应该是 string 类型的子集,如:string、字符串字面量类型
interface Person3 {
name: string
age?: number
[propName: string]: any
}
let james: Person3 = {
name: 'James',
gender: 'male',
}
// 类型“number”的属性“age”不能赋给“string”索引类型“string” ts(2411)
// interface Person4 {
// name:string
// age?:number
// [propName: string]: string
// }
- 鸭式辩型法
具有鸭子的特征就认为它就是鸭子,也就是通过制定规则判定对象是否实现这个接口
interface LabelVal {
label: string
}
function printLabel(labelObj: LabelVal) {
console.log(labelObj.label)
}
let myObj = { size: 10, label: 'Size 10 Object' }
printLabel(myObj) // Size 10 Object
// 在参数里写对象就相当于直接给labelObj赋值,因此不能多/少参数
// 但是如果将一个变量作为参数传递给函数,该变量不会进过额外属性检查,根据类型推论
// 得出 let myObj: { size: number; label: string } = { size: 10, label: "Size 10 Object" };
// 再将myObj赋值给labelObj,此时根据类型的兼容性,参照鸭式辩型法,都具有label属性,认定两者相同,利用此法绕开多余的类型检查
// printLabel({ size: 10, label: 'Size 10 Object' }) // 对象字面量只能指定已知属性,并且“size”不在类型“LabelVal”中。ts(2353)
let temp: LabelVal = {
label: '',
}
temp = myObj // ok
- 绕开额外属性检查的方式
// a.鸭式辩型法
// b.类型断言
interface Person5 {
name: string
age: number
money?: number
}
// 报错的:
// let p: Person5 = {
// name: "嘎嘎",
// age: 0,
// money: 10000,
// girl: false 对象字面量只能指定已知属性,并且“girl”不在类型“Person5”中。ts(2353)
// }
// 不会报错的:
let p: Person5 = {
name: '嘎嘎',
age: 0,
money: 10000,
girl: false,
} as Person5
// c.索引签名
interface Person6 {
name: string
age: number
money?: number
[key: string]: any
}
let p2: Person6 = {
name: '兔神',
age: 25,
money: -100000,
girl: false,
}
function testFunc(param: Person6) {
console.log(param.girl)
}
testFunc(p2) // false
十二、接口与类型别名的区别
type(类型别名)会给一个类型起个新名字。type 有时和 interface 很像,但是可以作用于原始值(基本类型),联合类型,元组以及其他任何你需要手写的类型。起别名不会新建一个类型 ,它创建了一个新名字来引用那个类型。
给基本类型起别名通常没什么用,尽管可以做为文档的一种形式使用
- Objects / Functions
// 两者都可以用来描述对象或函数的类型,但是语法不同
interface Point {
x: number
y: number
}
interface SetPoint {
(x: number, y: number): void
}
type Point1 = {
x: number
y: number
}
type SetPoint1 = (x: number, y: number) => void
- 与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组
type Name = string
type PartialPointX = { x: number }
type PartialPointY = { y: number }
type PartialPoint = PartialPointX | PartialPointY
type Data = [number, string]
let div = document.createElement('div')
type D = typeof div
- 接口可以定义多次,类型别名不可以
接口定义多次,会被自动合并为单个接口
interface Point2 {
x: number
}
interface Point2 {
y: number
}
const testPoint = { x: 1, y: 2 }
- 扩展
接口可以扩展类型别名,类型别名也可以扩展接口
接口扩展通过extends实现
类型别名扩展就是交叉类型,通过**&**实现
// 接口扩展接口:
interface InterPointX {
x: number
}
interface InterPointY extends InterPointX {
y: number
}
// 类型别名扩展类型别名:
type TypePointA = {
a: string
}
type TypePointB = TypePointA & {
b: number
}
// 接口扩展类型别名:
interface InterPointZ extends TypePointA {
z: number
}
// 类型别名扩展接口:
type TypePointC = InterPointX & {
y: number
}
十三、泛型
泛型是一个抽象类型,只有在调用的时候才能确定它的值
常见的泛型变量代表的意思:
K(key):表示对象中的键类型
V(value):表示对象中的值类型
E(element):表示元素类型
T(type):表示类型
function identity<T, U>(value: T, message: U): T {
console.log(message)
return value
}
console.log(identity<number, string>(68, 'Semlinker')) // 可以省略尖括号
console.log(identity(68, 'Semlinker')) // 系统自动检查参数类型
一. 泛型约束
理论上 T 可以是任何类型,不同于 any,你不管使用它的什么属性或者方法都会报错(除非这个属性和方法是所有集合共有的)。
// 报错:
// function trace<T>(arg: T): T{
// console.log(arg.size) // 报错:类型“T”上不存在属性“size” ts(2339)
// return arg
// }
// 正确:
// 由于T继承了Sizeable接口,所以T上是存在size属性的
interface Sizeable {
size: number
}
function trace<T extends Sizeable>(arg: T): T {
console.log(arg.size)
return arg
}
二. 泛型工具类型
ts 内置的工具类型(常用):Partial、Required、Readonly、Record、ReturnType 等
- typeof
主要用于获取变量或者属性的类型:
// 主要用于获取变量或者属性的类型
interface TPerson {
name: string
age: number
}
const sem: TPerson = { name: 'John', age: 30 }
type Sem = typeof sem // type Sem = TPerson
const lolo: Sem = {
name: '',
age: 0,
}
获取嵌套对象类型:
// 获取嵌套对象类型:
const Message = {
name: 'Jimmy',
age: 18,
address: {
province: '四川',
city: '成都',
},
}
type message = typeof Message
/*
type message = {
name: string;
age: number;
address: {
province: string;
city: string;
};
}
*/
获取函数对象类型:
// 获取函数对象类型:
function toArray(x: number): Array<number> {
return [x]
}
type Func = typeof toArray // type Func = (x: number) => Array<number>
- keyof
用于获取某种类型的所有键,其返回类型是联合类型
// 用于获取某种类型的所有键,其返回类型是联合类型
interface TPerson1 {
name: string
age: number
}
type k1 = keyof TPerson1 // "name" | "age"
type k2 = keyof TPerson1[] // "length" | "toString" | "pop" | "push" | "concat" | "join"
type k3 = keyof { [x: string]: TPerson1 } // string | number
function tv1(x: k1) {
console.log(x)
}
function tv2(x: k2) {
console.log(x)
}
function tv3(x: k3) {
console.log(x)
}
tv1('name')
tv1('age')
// tv1('class') 报错
tv2('length')
tv2('toString')
// tv2('assign') 报错
tv3('this')
tv3(111)
// tv3(false) 报错
ts 中指出两种索引签名,数字索引和字符串索引:
为了同时支持两种索引类型,就要求数字索引的返回值必须是字符串索引返回值的子类。
原因:当使用数字索引时,js 在执行索引操作时,会把数字索引先转换为字符串索引
// ts中指出两种索引签名,数字索引和字符串索引:
// 为了同时支持两种索引类型,就要求数字索引的返回值必须是字符串索引返回值的子类。
// 原因:当使用数字索引时,js在执行索引操作时,会把数字索引先转换为字符串索引
interface StringArray {
// 字符串索引 -> keyof StringArray => string | number
[index: string]: string
}
interface StringArray1 {
// 数字索引 -> keyof StringArray1 => number
[index: number]: string
}
let k4: keyof boolean // let k4: "valueOf"
let k5: keyof number // let k5: "toString" | "toLocaleString" | "valueOf" | "toFixed" | "toExponential" | "toPrecision"
let k6: keyof symbol // let k6: "toString" | typeof Symbol.toPrimitive | typeof Symbol.toStringTag | "valueOf" | "description"
keyof 使用的例子:
// keyof的作用
// obj中并不一定具有key变量值的属性,以下方法不是很好的解决方式:
// function prop(obj: object, key: string) {
// return obj[key] // 元素隐式具有 "any" 类型,因为类型为 "string" 的表达式不能用于索引类型 "{}"。在类型 "{}" 上找不到具有类型为 "string" 的参数的索引签名。ts(7053)
// return (obj as any)[key] // 暴力解决(不建议使用)
// }
// 正确的解决方式:
// 限制key变量的范围,如下方式限制key为K类型,K类型是keyof T的子类型
// keyof T返回的是T类型的所有键的联合类型
// 即:key中的值被限定在obj对象所有键的联合类型中
function prop<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
type Todo = {
id: number
text: string
done: boolean
}
const todo: Todo = {
id: 1,
text: 'Learn TypeScript keyof',
done: false,
}
function prop1<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
const tId = prop1(todo, 'id') // const tId: number
const tText = prop1(todo, 'text') // const tText: string
const tDone = prop1(todo, 'done') // const tDone: boolean
// const tDate = prop1(todo, 'date') // 类型“"date"”的参数不能赋给类型“keyof Todo”的参数 ts(2345)
console.log(tId, tText, tDone) // 1 Learn TypeScript keyof false
- in
in 用来遍历枚举类型
// in 用来遍历枚举类型:
type Keys = 'a' | 'b' | 'c'
type Obj = {
[p in Keys]: any
} // type Obj = { a: any; b: any; c: any; }
- infer
在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用
type ReturnType1<T> = T extends (...args: any[]) => infer R ? R : any
- extends
用于继承某些类,添加泛型约束
interface Lengthwise {
length: number
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
// console.log(arg.size) // 类型“T”上不存在属性“size”。ts(2339)
return arg
}
// loggingIdentity(5) 类型“number”的参数不能赋给类型“Lengthwise”的参数。ts(2345)
loggingIdentity({ length: 5, value: 7 }) // 传入的值必须包含length属性
- 索引类型
场景:在对象中获取一些属性值,然后建立对应的集合
// 场景:在对象中获取一些属性值,然后建立对应的集合
let person = {
name: 'musion',
age: 35,
}
function getValues(person: any, keys: string[]) {
return keys.map(key => person[key])
}
console.log(getValues(person, ['name', 'age'])) // ['musion', 35]
console.log(getValues(person, ['gender'])) // [undefined]
// 如上代码,getValues(person, ['gender']) 执行的结果是 [undefined] ,但是ts编译器并没有给出报错信息
// 那么如何使用ts对这种模式进行类型约束呢?通过索引类型查询 和 索引访问 操作符
// 如下:
// T[K]:表示对象T的属性K所表示的类型
// T[K][]:表示变量T取属性k的值的数组
function getValues1<T extends object, K extends keyof T>(person: T, keys: K[]): T[K][] {
return keys.map(key => person[key])
}
interface TPerson2 {
name: string
age: number
}
const tPerson: TPerson2 = {
name: '张三',
age: 19,
}
getValues1(tPerson, ['name'])
// getValues1(tPerson, ['gender']) 不能将类型“"gender"”分配给类型“keyof TPerson2”。ts(2322)
// 通过[]索引类型访问操作符,得到某个所以你的类型
class Person {
name: string = 'bob'
age: number = 18
}
type MyType = Person['name'] // type MyType = string
- 映射类型
根据旧的类型创建出新的类型,我们称为映射类型
通过 +/- 来制定添加还是删除
interface TestInterface {
name: string
age: number
}
// 将TestInterface接口里的属性全部变成可选
type OptionalTestInterface<T> = {
[p in keyof T]+?: T[p]
}
type newTestInterface = OptionalTestInterface<TestInterface>
/*
type newTestInterface = {
name?: string;
age?: number;
}
*/
// 将TestInterface接口里的属性全部变成只读
type OptionalTestInterface1<T> = {
+readonly [p in keyof T]+?: T[p]
}
type newTestInterface1 = OptionalTestInterface1<TestInterface>
/*
type newTestInterface1 = {
readonly name?: string;
readonly age?: number;
}
*/
十四、内置的工具类型
Partial:将类型的属性变成可选的
将类型的属性变成可选的
注意:partial有局限性,只支持处理第一层的属性
// 定义:
/*
type Partial<T> = {
[P in keyof T]?: T[P]
}
*/
// 举例:
interface UserInfo {
id: string
name: string
}
/*
报错:类型 "{ name: string; }" 中缺少属性 "id",但类型 "UserInfo" 中需要该属性。ts(2741)
const xiaomi: UserInfo = {
name:'xiaomi'
}
*/
// 使用 Partial<T>
type NewUserInfo = Partial<UserInfo>
/*
type NewUserInfo = {
id?: string;
name?: string;
}
*/
const zhangsan: NewUserInfo = {
name: '张三',
}
- 局限性:
// 局限性:
interface UserInfo1 {
id: string
name: string
fruits: {
appleNumber: number
orangeNumber: number
}
}
type NewUserInfo1 = Partial<UserInfo1>
/*
很清楚的发现fruits中的属性并没有变成可选属性
type NewUserInfo1 = {
id?: string;
name?: string;
fruits?: {
appleNumber: number;
orangeNumber: number;
};
}
类型 "{ appleNumber: number; }" 中缺少属性 "orangeNumber",但类型 "{ appleNumber: number; orangeNumber: number; }" 中需要该属性。ts(2741)
const userInfo1: NewUserInfo1 = {
id:'2222',
fruits:{
appleNumber: 321
}
}
*/
- 自定义 Partial 工具,支持处理多层:
// 自定义Partial工具,支持处理多层:
type DeepPartial<T> = {
[U in keyof T]?: T[U] extends object ? DeepPartial<T[U]> : T[U]
}
type NewUserInfo2 = DeepPartial<UserInfo1>
const userInfo2: NewUserInfo2 = {
id: '11111',
fruits: {
appleNumber: 123,
},
}
Required:将类型的属性变成必选
将类型的属性变成必选
注意:Required 与 Partial 一样具有局限性,只支持处理第一层的属性
/*
定义:
type Required<T> = {
[P in keyof T] -?: T[P]
}
*/
interface Car {
name?: string
price?: number
config?: {
color?: string
tyre?: string
}
}
type NewCar = Required<Car>
/*
type NewCar = {
name: string;
price: number;
config: {
color?: string;
tyre?: string;
};
}
*/
Readonly:将某个类型所有属性变为只读属性
将某个类型所有属性变为只读属性,也就意味着这些属性不能被重新赋值
注意:Readonly 同意 Partial 一样,都具有局限性,只支持处理第一层的属性
/*
定义:
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
*/
interface Water {
type: string
readonly name: string
config: {
origin: string
}
}
type NewWater = Readonly<Water>
/*
type NewWater = {
readonly type: string;
readonly name: string;
readonly config: {
origin: string;
};
}
*/
const newWater: NewWater = {
type: '饮用水',
name: '哇哈哈',
config: {
origin: '不知道',
},
}
// newWater.name = '农夫山泉' 无法为“name”赋值,因为它是只读属性。ts(2540)
newWater.config.origin = '山东'
Pick:从某个类型中挑出一些属性出来
从某个类型中挑出一些属性出来
/*
定义:
type Pick<T,K extends keyof T> = {
[P in K]: T[P]
}
*/
interface Country {
name: string
desc: string
population: string
}
type NewCountry = Pick<Country, 'desc' | 'name'>
/*
type country = {
name: string;
desc: string;
}
*/
const china: NewCountry = {
name: '',
desc: '',
// population: "" // 对象字面量只能指定已知属性,并且“population”不在类型“NewCountry”中。ts(2353)
}
Record:将属性的值类型转化为指定类型
Record<K extends keyof any, T>的作用是将 K 中的所有属性的值转化为 T 类型
/*
定义:
type Record<K extends keyof any, T> = {
[P in K]: T;
};
*/
interface Fruits {
name: string
}
type Fruits1 = 'apple' | 'banana' | 'orange'
const fruit: Record<Fruits1, Fruits> = {
apple: {
name: '苹果',
},
banana: {
name: '香蕉',
},
orange: {
name: '橙子',
},
}
ReturnType:用来得到一个函数的返回值类型
/*
定义:
type ReturnType<T extends (...args: any[]) => any> = T extends (
...args: any[]
) => infer R
? R
: any;
*/
type tFunc = (value: number) => string
const tFoo: ReturnType<tFunc> = 'this is foo'
Exclude:排除某些指定的类型
Exclude<T,U>的作用是将某个类型中属于另一个的类型移除掉,
即:在 T 类型中排除 U 类型
/*
定义:
type Exclude<T, U> = T extends U ? never : T;
*/
type T0 = Exclude<'a' | 'b' | 'c', 'a'> // type T0 = "b" | "c"
type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'b'> // type T1 = "c"
type T2 = Exclude<string | number | (() => void), Function> // type T2 = string | number
Extract:提取指定类型
Extract<T,U>的作用是从 T 中提取出 U
即:取 T 类型与 U 类型的交集
/*
定义:
type Extract<T, U> = T extends U ? T : never;
*/
type T3 = Extract<'a' | 'b' | 'c', 'a' | 'f'> // type T3 = "a"
type T4 = Extract<string | number | (() => void), Function> // type T4 = () => void
Omit:排除指定属性,构造新的类型
Omit<T,K extends keyof any>的作用是使用 T 类型中除了 K 类型的所有属性,来构造一个新的类型
/*
定义:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
*/
interface School {
name: string
address: string
number: string
desc: string
}
type NewSchool = Omit<School, 'name'>
/*
type NewSchool = {
number: string;
desc: string;
address: string;
}
*/
const school: NewSchool = {
number: '1000',
address: '中国',
desc: '不知名的学校',
// name:'哈哈哈学校' // 对象字面量只能指定已知属性,并且“name”不在类型“NewSchool”中。ts(2353)
}
NonNullable:过滤类型中的 null 和 undefined 类型
NonNullable 的作用是用来过滤类型中的 null 和 undefined 类型
/*
定义:
type NonNullable<T> = T extends null | undefined ? never : T
*/
type T5 = NonNullable<string | undefined | number> // type T5 = string | number
type T6 = NonNullable<string[] | null | undefined> // type T6 = string[]
interface NullType {
name: null
age: undefined | string
gender?: string
}
type T7 = NonNullable<NullType>
// 不报错,也没有效果
const pp: T7 = {
name: null,
age: undefined,
}
Parameters:获取函数的“参数类型”组成的元组类型
Parameters的作用是用于获取函数的“参数类型”组成的元组类型
/*
定义:
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any? P : never;
*/
type T8 = Parameters<() => void> // type T8 = []
type T9 = Parameters<typeof Array.isArray> // type T9 = [arg: any]
type T10 = Parameters<typeof parseInt> // type T10 = [string: string, radix?: number | undefined]
type T11 = Parameters<typeof Math.max> // type T11 = number[]
type T12 = Parameters<Math['max']> // type T12 = number[]
十五、tsconfig.json 介绍
tsconfig.json 是 TypeScript 项目的配置文件。如果一个目录下存在一个 tsconfig.json 文件,那么往往意味着这个目录就是 TypeScript 项目的根目录。
tsconfig.json 包含 TypeScript 编译的相关配置,通过更改编译配置项,我们可以让 TypeScript 编译出 ES6、ES5、node 的代码。tsconfig.json 重要字段: > files - 设置要编译的文件的名称;
include - 设置需要进行编译的文件,支持路径模式匹配;
exclude - 设置无需进行编译的文件,支持路径模式匹配;
compilerOptions - 设置与编译流程相关的选项。
// compilerOptions 选项:
const tsConfig = {
compilerOptions: {
/* 基本选项 */
target: 'es5', // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
module: 'commonjs', // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
lib: [], // 指定要包含在编译中的库文件
allowJs: true, // 允许编译 javascript 文件
checkJs: true, // 报告 javascript 文件中的错误
jsx: 'preserve', // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
declaration: true, // 生成相应的 '.d.ts' 文件
sourceMap: true, // 生成相应的 '.map' 文件
outFile: './', // 将输出文件合并为一个文件
outDir: './', // 指定输出目录
rootDir: './', // 用来控制输出目录结构 --outDir.
removeComments: true, // 删除编译后的所有的注释
noEmit: true, // 不生成输出文件
importHelpers: true, // 从 tslib 导入辅助工具函数
isolatedModules: true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
/* 严格的类型检查选项 */
strict: true, // 启用所有严格类型检查选项
noImplicitAny: true, // 在表达式和声明上有隐含的 any类型时报错
strictNullChecks: true, // 启用严格的 null 检查
noImplicitThis: true, // 当 this 表达式值为 any 类型的时候,生成一个错误
alwaysStrict: true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
/* 额外的检查 */
noUnusedLocals: true, // 有未使用的变量时,抛出错误
noUnusedParameters: true, // 有未使用的参数时,抛出错误
noImplicitReturns: true, // 并不是所有函数里的代码都有返回值时,抛出错误
noFallthroughCasesInSwitch: true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
/* 模块解析选项 */
moduleResolution: 'node', // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
baseUrl: './', // 用于解析非相对模块名称的基目录
paths: {}, // 模块名到基于 baseUrl 的路径映射的列表
rootDirs: [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
typeRoots: [], // 包含类型声明的文件列表
types: [], // 需要包含的类型声明文件名列表
allowSyntheticDefaultImports: true, // 允许从没有设置默认导出的模块中默认导入。
/* Source Map Options */
sourceRoot: './', // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
mapRoot: './', // 指定调试器应该找到映射文件而不是生成文件的位置
inlineSourceMap: true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
inlineSources: true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
/* 其他选项 */
experimentalDecorators: true, // 启用装饰器
emitDecoratorMetadata: true, // 为装饰器提供元数据的支持
},
}