TypeScript
环境搭建
yarn create vite
选择vanilla-ts
ts 类型声明空间与变量声明空间
- ts 中的类型声明空间与变量声明空间是分开的,这是因为 ts 是静态类型语言,所以在编译阶段就需要知道变量的类型,而不是在运行时才知道变量的类型。
// 变量声明空间
let a: number = 1
a = '1' // error
// 类型声明空间
type A = number
let a: A = 1
a = '1' // error
// 类在ts 中既是变量声明空间,也是类型声明空间
class Foo {}
type A = number
let a = Foo
类型注解与类型推断
- 类型注解:我们来告诉 ts 变量是什么类型
// 类型注解: Number类型
let a: number
a = 1
a = '1' // error
- 类型推断:ts 会自动的去尝试分析变量的类型
// ts 自动推断 a 的类型为 number
let a = 1
a = '1' // error
类型别名
- 类型别名用来给一个类型起个新名字
- 基本类型:
string
,number
,boolean
,symbol
,undefined
,null
,bigint
,可以直接作为类型别名 - 对象类型:
[]
,{}
,class
,function
,可以直接作为类型别名 TS
新增类型:never
,any
,unknown
,void
,enum
, 可以直接作为类型别名
// 类型别名 字符串
type Name = string
// 函数类型别名,返回值为字符串
type NameResolver = () => string
// 联合类型
type NameOrResolver = Name | NameResolver
// 交叉类型(用户对象类型)
// 参数为函数或者是字符串,返回值为字符串
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n
} else {
return n()
}
}
字符串字面量类型
字符串字面量类型用来约束取值只能是某几个字符串中的一个
联合类型
type EventNames = 'click' | 'scroll' | 'mousemove'
function handleEvent(ele: Element, event: EventNames) {
// do something
}
handleEvent(document.getElementById('hello'), 'scroll') // 没问题
handleEvent(document.getElementById('world'), 'dbclick') // 报错,event 不能为 'dbclick'
- 交叉类型
interface DogInterface {
run(): void
}
interface CatInterface {
jump(): void
}
let pet: DogInterface & CatInterface = {
run() {},
jump() {},
}
never 类型 any 类型 unknown 类型
- never 类型表示的是那些永不存在的值的类型
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message)
}
// 返回never
function fail(): never {
return error('Something failed')
}
// 返回never
function infiniteLoop(): never {
while (true) {}
}
- any 类型表示的是允许赋值为任意类型
let a: any = 1
a = '1'
a = true
- unknown 类型表示的是未知类型的值,但是在使用之前需要进行类型判断
let a: unknown = 1
a = '1'
a = true
let b = a // error
// 使用前进行类型判断
if (typeof a === 'string') {
let c = a // ok
}
数组与元组
- 数组(Array)合并了相同类型的对象
// 数组
let fibonacci: number[] = [1, 1, 2, 3, 5]
console.log(fibonacci[0]) // 1
// 数组泛型 另外一种声明方式
let fibonacci: Array<number> = [1, 1, 2, 3, 5]
console.log(fibonacci[0]) // 1
- 元组(Tuple)合并了不同类型的对象
let tom: [string, number] = ['Tom', 25]
console.log(tom[0]) // Tom
枚举与 const 枚举
- 枚举(Enum)类型用于取值被限定在一定范围内的场景
enum Days {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat,
}
console.log(Days['Sun'] === 0) // true
console.log(Days['Mon'] === 1) // true
console.log(Days['Tue'] === 2) // true
console.log(Days['Sat'] === 6) // true
// 双向映射
console.log(Days[0] === 'Sun') // true
console.log(Days[1] === 'Mon') // true
console.log(Days[2] === 'Tue') // true
console.log(Days[6] === 'Sat') // true
- const 枚举
// const 枚举
const enum Directions {
Up,
Down,
Left,
Right,
}
let directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right,
]
console.log(directions) // [0, 1, 2, 3]
// const 枚举 字符串
const enum Directions {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
let directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right,
]
console.log(directions) // ["UP", "DOWN", "LEFT", "RIGHT"]
对象类型与索引签名
- 对象类型
// 对象类型
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25,
}
- 索引签名: 索引签名描述了对象索引的类型,有两种索引签名:字符串和数字
interface Person {
name: string
age: number
// 索引签名: 可以添加额外的属性
[propName: string]: any
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'female',
}
类型断言与非空断言
- 类型断言: 用来手动指定一个值的类型, 语法为:
<类型>值
或值 as 类型
let someValue: any = 'this is a string'
// as 告诉编译器,我知道这个值是 string 类型
let strLength: number = (someValue as string).length
- 非空断言: 用来告诉编译器变量不会是 null 或 undefined, 语法为:
变量名!
function getLength(something: string | null): number {
if (something === null) {
return 0
} else {
// 非空断言
return something!.length
}
}
函数类型与 void 类型
- 函数类型
// 声明函数类型
interface SearchFunc {
(source: string, subString: string): boolean
}
// 函数类型变量
let mySearch: SearchFunc = function (source: string, subString: string) {
return source.search(subString) !== -1
}
// 调用函数
mySearch('hello', 'll') // true
- void 类型: 用来表示没有任何返回值的函数
function alertName(): void {
alert('My name is Tom')
}
函数重载与可调用注解
- 函数重载: 为同一个函数提供多个函数类型定义来进行函数重载
function reverse(x: number): number
function reverse(x: string): string
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
// 数字
return Number(x.toString().split('').reverse().join(''))
} else if (typeof x === 'string') {
// 字符串
return x.split('').reverse().join('')
}
}
- 可调用注解: 用来约束函数的类型, 对函数重载有很好的支持
interface SearchFunc {
(source: string, subString: string): boolean
(source: number, subString: number): boolean
}
// 函数类型变量,SearchFunc 为可调用注解,来约束函数的类型
let mySearch: SearchFunc = function (source: string, subString: string) {
if (typeof source === 'string' && typeof subString === 'string') {
return source.search(subString) !== -1
} else if (typeof source === 'number' && typeof subString === 'number') {
return source.toString().search(subString.toString()) !== -1
}
}
// 调用函数
mySearch('hello', 'll') // true
mySearch(123456, 456) // true
接口与类型别名区别
区别
- 类型别名可以直接赋值给一个变量, 接口只能赋值对象类型。
// 类型别名可以直接赋值给一个变量或者对象 type Name = string let tom: Name = 'Tom' // 接口只能是对象 interface Person { name: string } let tom: Person = { name: 'Tom', }
- 类型别名不能被
extends
和implements
, 而接口可以
// 类型别名不能被 extends 和 implements type Person = { name: string } interface Teacher extends Person {} // error // 接口可以被 extends 和 implements interface Person { name: string } interface Teacher extends Person {} class User implements Person {}
- 类型别名不能合并, 而接口可以合并
// 类型别名不能合并 type Person = { name: string } type Person = { age: number } // error // 接口可以合并 interface Person { name: string } interface Person { age: number }
- 类型别名映射类型写法, 而接口不行
// 类型别名映射类型写法 type Person = { name: string age: number } // 通过映射类型,将 Person 的所有属性变为只读 type ReadonlyPerson = { readonly [P in keyof Person]: Person[P] } // 接口不行 interface Person { name: string age: number } interface ReadonlyPerson { readonly [P in keyof Person]: Person[P] } // error
接口
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25,
}
- 类型别名
type Person = {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25,
}
字面量类型和 keyof 关键字
- keyof 关键字
interface Person {
name: string
age: number
}
// keyof Person 表示取出 Person 的所有属性名组成的联合类型
type PersonKeys = keyof Person // 'name' | 'age'
- 字面量类型: 用来约束变量的取值只能是某几个值中的一个
type Directions = 'Up' | 'Down' | 'Left' | 'Right'
function move(direction: Directions) {
// ...
}
move('Up') // ok
move('Left') // ok
move('A') // error
泛型和泛型常见操作
- 泛型: 一种类型变量,用来表示类型,而不是具体的值
// 泛型函数
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
// 输出
createArray<string>(3, 'x') // ['x', 'x', 'x']
createArray<number>(3, 1) // [1, 1, 1]
createArray<boolean>(3, true) // [true, true, true]
- 泛型常见操作
// 泛型约束
interface Lengthwise {
length: number
}
// 泛型函数
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length) // Now we know it has a .length property, so no more error
return arg
}
// 错误调用
loggingIdentity(3) // error
// 正确调用
loggingIdentity({ length: 10, value: 3 }) // ok
// 泛型接口
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>
}
// 泛型函数,约束泛型 T
let createArray: CreateArrayFunc = function <T>(
length: number,
value: T
): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
// 调用
createArray<string>(3, 'x') // ['x', 'x', 'x']
createArray<number>(3, 1) // [1, 1, 1]
createArray<boolean>(3, true) // [true, true, true]
// 泛型类
class GenericNumber<T> {
zeroValue: T
add: (x: T, y: T) => T
}
let myGenericNumber = new GenericNumber<number>()
myGenericNumber.zeroValue = 0
myGenericNumber.add = function (x, y) {
return x + y
}
// 输出 add 方法的返回值 数字
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 1)) // 1
let stringNumeric = new GenericNumber<string>()
stringNumeric.zeroValue = ''
stringNumeric.add = function (x, y) {
return x + y
}
// 输出 add 方法的返回值 字符串
console.log(stringNumeric.add(stringNumeric.zeroValue, 'test')) // test
类型兼容性
- 类型兼容性: 一个类型 X 可以被赋值给另一个类型 Y,那么我们就可以说类型 X 兼容类型 Y
interface Named {
name: string
}
class Person {
name: string
}
let p: Named
p = new Person() // ok
let x: Named
let y = { name: 'Alice', location: 'Seattle' }
x = y // ok
类型保护
- 类型保护: 用来确保变量在某个作用域内的类型,可以在此作用域内放心的引用此类型的属性,或者调用此类型的方法
// typeof 类型保护
function padLeft(value: string, padding: string | number) {
if (typeof padding === 'number') {
return Array(padding + 1).join(' ') + value
}
if (typeof padding === 'string') {
return padding + value
}
throw new Error(`Expected string or number, got '${padding}'.`)
}
// instanceof 类型保护
interface Padder {
getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(' ')
}
}
class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value
}
}
function getRandomPadder() {
return Math.random() < 0.5
? new SpaceRepeatingPadder(4)
: new StringPadder(' ')
}
// 类型为 'SpaceRepeatingPadder' | 'StringPadder'
let padder: Padder = getRandomPadder()
if (padder instanceof SpaceRepeatingPadder) {
padder // 类型细化为 'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder // 类型细化为 'StringPadder'
}
// in 类型保护
interface Xiaoming {
usename: string
}
interface Xiaohong {
age: number
}
function getSmallPet(n: Xiaoming | Xiaohong): string | number {
if ('usename' in n) {
return n.usename
}
if ('age' in n) {
return n.age
}
}
// 调用
getSmallPet({ usename: 'xiaoming' }) // xiaoming
// null 类型保护
function f(sn: string | null): string {
if (sn == null) {
return 'default'
} else {
return sn
}
}
// 可辨识联合
interface Square {
kind: 'square'
size: number
}
interface Rectangle {
kind: 'rectangle'
width: number
height: number
}
interface Circle {
kind: 'circle'
radius: number
}
type Shape = Square | Rectangle | Circle
// 通过类型保护,可以访问联合类型的所有类型的属性
function area(s: Shape) {
switch (s.kind) {
case 'square':
return s.size * s.size
case 'rectangle':
return s.height * s.width
case 'circle':
return Math.PI * s.radius ** 2
}
}
// 用户自定义的类型保护
interface Bird {
fly(): void
layEggs(): void
}
interface Fish {
swim(): void
layEggs(): void
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet()
// as 类型断言
if ((pet as Fish).swim) {
;(pet as Fish).swim()
} else if ((pet as Bird).fly) {
;(pet as Bird).fly()
}
映射类型和内置工具类型
- 映射类型
// 映射类型
type A = {
name: string
age: number
}
console.log('A', A) // { name: string; age: number; }
type B = {
readonly [K in keyof A]: number
}
console.log('B', B) // { readonly name: number; readonly age: number; }
type C = {
-readonly [K in keyof A]: number
}
console.log('C', C) // { name: number; age: number; }
- 内置工具类型
// Readonly<T>: 将所有属性设置为只读
interface Todo {
title: string
}
const todo: Readonly<Todo> = {
title: 'Delete inactive users',
}
// todo.title 自读属性,不能修改
todo.title = 'Hello' // error
// Partial<T>: 将所有属性设置为可选
interface Todo {
title: string
description: string
}
console.log('Todo', Todo) // { title: string; description: string; }
console.log('Partial<Todo>', Partial<Todo>) // { title?: string | undefined; description?: string | undefined; }
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate }
}
const todo1 = {
title: 'organize desk',
description: 'clear clutter',
}
const todo2 = updateTodo(todo1, {
description: 'throw out trash',
})
// Record<K, T>: 用于将类型K中的每个属性的值转换为类型T
interface PageInfo {
title: string
}
type Page = 'home' | 'about' | 'contact'
const x: Record<Page, PageInfo> = {
about: { title: 'about' },
contact: { title: 'contact' },
home: { title: 'home' },
}
console.log('Record<Page, PageInfo>', Record<Page, PageInfo>) // { about: PageInfo; contact: PageInfo; home: PageInfo; }
// Pick<T, K>: 从T中选择一系列属性K
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>
console.log('TodoPreview', TodoPreview) // { title: string; completed: boolean; }
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
// Omit<T, K>: 从T中删除一系列属性K
type TodoPreview = Omit<Todo, 'description'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
console.log('TodoPreview', TodoPreview) // { title: string; completed: boolean; }
// Exclude<T, U>: 从T中剔除可以赋值给U的类型
type T0 = Exclude<'a' | 'b' | 'c', 'a'> // "b" | "c"
type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'b'> // "c"
type T2 = Exclude<string | number | (() => void), Function> // string | number
// Extract<T, U>: 提取 T 中可以赋值给 U 的类型
type T0 = Extract<'a' | 'b' | 'c', 'a' | 'f'> // "a"
type T1 = Extract<string | number | (() => void), Function> // () => void
// NonNullable<T>: 从T中剔除null和undefined
type T0 = NonNullable<string | number | undefined> // string | number
type T1 = NonNullable<string[] | null | undefined> // string[]
// ReturnType<T>: 获取函数返回值类型
declare function f1(): { a: number; b: string }
type T0 = ReturnType<() => string> // string
type T1 = ReturnType<(s: string) => void> // void
type T2 = ReturnType<<T>() => T> // {}
type T3 = ReturnType<<T extends U, U extends number[]>() => T> // number[]
type T4 = ReturnType<typeof f1> // { a: number, b: string }
type T5 = ReturnType<any> // any
type T6 = ReturnType<never> // any
type T7 = ReturnType<string> // Error
type T8 = ReturnType<Function> // Error
// InstanceType<T>: 获取构造函数类型的实例类型
class C {
x = 0
y = 0
}
type T0 = InstanceType<typeof C> // C
type T1 = InstanceType<any> // any
type T2 = InstanceType<never> // any
type T3 = InstanceType<string> // Error
type T4 = InstanceType<Function> // Error
// Required<T>: 将所有属性设置为必选
interface Props {
a?: number
b?: string
}
const obj: Props = { a: 5 } // OK
const obj2: Required<Props> = { a: 5 } // Error: property 'b' missing
// ThisParameterType<T>: 获取函数类型的 this 参数类型
interface Options {
method: 'GET' | 'POST'
data?: string
}
function makeRequest(url: string, options: Options) {
/* ... */
}
function makeRequest(
url: string,
options: Options & ThisParameterType<typeof makeRequest>
) {
/* ... */
}
makeRequest('/api', {
method: 'GET',
data: 'hello',
onSuccess(data) {
this // 类型为 Options & { onSuccess(data: string): void }
},
onError(error) {
this // 类型为 Options & { onError(error: Error): void }
},
})
// OmitThisParameter<T>: 从函数类型中移除 this 参数
interface Options {
method: 'GET' | 'POST'
data?: string
}
function makeRequest(url: string, options: Options) {
/* ... */
}
function makeRequest(url: string, options: OmitThisParameter<Options>) {
/* ... */
}
makeRequest('/api', {
method: 'GET',
data: 'hello',
onSuccess(data) {
this // 类型为 Options
},
onError(error) {
this // 类型为 Options
},
})
// Parameters<T>: 获取函数类型的参数类型
function f1(a: number, b: string) {
return b
}
type T0 = Parameters<() => string> // []
type T1 = Parameters<(s: string) => void> // [s: string]
type T2 = Parameters<<T>(arg: T) => T> // [arg: unknown]
type T3 = Parameters<typeof f1> // [a: number, b: string]
type T4 = Parameters<any> // unknown[]
type T5 = Parameters<never> // never[]
// ConstructorParameters<T>: 获取构造函数类型的参数类型
class C {
x = 0
y = 0
}
type T0 = ConstructorParameters<ErrorConstructor> // [message?: string]
type T1 = ConstructorParameters<FunctionConstructor> // string[]
type T2 = ConstructorParameters<RegExpConstructor> // [pattern: string | RegExp, flags?: string]
type T3 = ConstructorParameters<typeof C> // []
type T4 = ConstructorParameters<any> // unknown[]
type T5 = ConstructorParameters<never> // never[]
条件类型 和 infer 关键字
- 条件类型: 初始状态并不直接确定具体类型,而是根据条件判断后确定具体类型
// T extends U ? X : Y
type TypeName<T> = T extends string
? 'string'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: T extends undefined
? 'undefined'
: T extends Function
? 'function'
: 'object'
type T0 = TypeName<string> // "string"
type T1 = TypeName<'a'> // "string"
type T2 = TypeName<() => void> // "function"
// 分布式条件类型
type T10 = TypeName<string | (() => void)> // "string" | "function"
type T11 = TypeName<string | string[] | undefined> // "string" | "object" | "undefined"
type T12 = TypeName<string[] | number[]> // "object"
// 条件类型的分布式特性
type Diff<T, U> = T extends U ? never : T // Remove types from T that are assignable to U
type Filter<T, U> = T extends U ? T : never // Remove types from T that are not assignable to U
type T20 = Diff<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'> // "b" | "d"
type T21 = Filter<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'> // "a" | "c"
type T22 = Diff<string | number | (() => void), Function> // string | number
type T23 = Filter<string | number | (() => void), Function> // () => void
type T24 = Diff<string | string[] | null | undefined, string[]> // string | null | undefined
type T25 = Filter<string | string[] | null | undefined, string[]> // string[]
type T26 = Diff<undefined, null> // undefined
type T27 = Filter<undefined, null> // never
// 条件类型的约束
type NonNullable<T> = T extends null | undefined ? never : T
type T30 = NonNullable<string | number | undefined> // string | number
type T31 = NonNullable<string[] | null | undefined> // string[]
// 条件类型的类型推断
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any
type T40 = ReturnType<() => string> // string
type T41 = ReturnType<(s: string) => void> // void
type T42 = ReturnType<<T>() => T> // {}
type T43 = ReturnType<<T extends U, U extends number[]>() => T> // number[]
type T44 = ReturnType<typeof f1> // { a: number, b: string }
// 条件类型的类型推断
type Unpacked<T> = T extends (infer U)[]
? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U>
? U
: T
type T50 = Unpacked<string> // string
- infer: 用于在条件类型中推断类型,(定义类型)
// infer 关键字
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any
type T40 = ReturnType<() => string> // string
type T41 = ReturnType<(s: string) => void> // void
type T42 = ReturnType<<T>() => T> // {}
type T43 = ReturnType<<T extends U, U extends number[]>() => T> // number[]
type T44 = ReturnType<typeof f1> // { a: number, b: string }
// infer 关键字的使用
type Unpacked<T> = T extends (infer U)[]
? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U>
? U
: T
type T50 = Unpacked<string> // string
type T51 = Unpacked<string[]> // string
type T52 = Unpacked<() => string> // string
type T53 = Unpacked<Promise<string>> // string
type T54 = Unpacked<Promise<string>[]> // Promise<string>
type T55 = Unpacked<Unpacked<Promise<string>[]>> // string
类中使用类型
// 类中使用类型
class C {
x = 0
y = 0
}
// 类当做类型使用
type T00 = C['x'] // number
type T01 = C['x'][] // number[]
type T02 = C['x' | 'y'] // number
type T03 = C['x' | 'z'] // number | undefined
type T04 = C extends { x: infer U } ? U : never // number
type T05 = C extends { x: (args: infer U) => void } ? U : never // never
type T06 = ReturnType<C['getX']> // string
// 类中实现接口
interface I {
x: number
y: number
}
class C implements I {
x = 0
y = 0
}
type T10 = C['x'] // number
type T11 = C['x'][] // number[]
// 类使用泛型
class C<T> {
x: T
y: T
}
class D extends C<string> {
z = 0
}
type T20 = D['x'] // string
type T21 = D['x'][] // string[]
type T22 = D['x' | 'y'] // string
type T23 = D['x' | 'z'] // string | number