Typescript 拾遗
date
May 20, 2024
slug
relearn-typescript
status
Published
tags
Typescript
summary
Typescript 知识点总结
type
Post
基础类型注解类型推导常用类型注意点关键字typeofinstanceofisaskeyofinconstinferdeclare内置类型ExtractExcludeRecordPickOmitReuqiredPartial类基础静态成员单例条件类型示例泛型定义应用默认值与约束反向赋值(推断)细节函数重载定义示例改进泛型函数声明文件实现作用域命名空间模块声明tsconfig.json作用域范围extendscompilerOptions参考
下文中的“签名”可以理解为“定义“或”声明“
基础
类型注解
const data: number = 3
类型推导
const data = 3
常用类型
- 基本类型
null
undefined
number/string/boolean/symbol/null/undefined
const data1: string = '1'
const data2: String = '2' // String 是 string 的包装器(string 是 String 的实例),在 ts 中一般使用 string
type StrTemp = `1` // 字符串模版类型,也是字符串类型
type StrTemp2<T> = `${T}` // error: Type 'T' is not assignable to type 'string | number | bigint | boolean | null | undefined'
type StrTemp3<T> = `${T & string}`
参考下方
undefined
// js env
let data1 = null
console.log(typeof data1) // object
let data2
console.log(data2) // undefined
// ts env
let data1: string | undefined
console.log(data1) // undefined
let data2: string = undefined // Only open "strict: true" in tsconfig, error: Type 'undefined' is not assignable to type 'string'
function callback(data?: string) {} // optional
callback()
function callback(data: string | undefined) {}
callback() // error: An argument for 'data' was not provided
// only undefined/any/unknown type can recive undefined value
let data3: undefined = undefined
let data4: any = undefined
let data5: unknown = undefined
- 根类型
Object/{}
const data1: Object = null // error
const data2: Object = undefined // error
const data3: Object = 1
const data4: Object = '1'
const data5: Object = !!1
const data6: Object = Symbol<string>('1')
const data7: Object = new Set<string>()
// {} 是 Object 类型简写
- 对象类型
Array/object/function
const data1: object = 1 // error
const data2: object = { name: '1' }
- 枚举
- 数字枚举
- 字符串枚举
enum Roles {
owner,
maintainer,
developer
}
Roles['owner'] // 1
Roles[1] // 'maintainer'
// 默认值为 number 类型可反取,为 string 类型不支持反取
enum ApprovalStatusTips {
dealing = 'deal with {role}',
waiting = 'waiting for {role}',
complete = 'complete'
}
ApprovalStatusTips['dealing'] // 'deal with {role}'
ApprovalStatusTips[1] // error: Property '1' does not exist on type 'typeof ApprovalStatusTips'
- 其他类型
any
可以是任何类型的”父类“或”子类“,可以从中获取任意名称的属性与方法unknown
可以是任何类型的”父类“但不是”子类“,不能从中获取任意名称的属性与方法never
void
tuple
- 在定义时每个元素类型都确定
- 元素值的数据类型必须是当前元素定义的类型
- 元素值个数与定义时个数相同
- 可变
tuple
any/unknown/never/void/元组(tuple)/可变元组
下列描述中的“父类“指等号左侧变量,”子类“指等号右侧变量
const data1: string[] = ['1']
// const data1: number = 1
// const data1 = undefined
const data2: any = data1
const data3: any = ['2']
// const data3: any = 2
// const data3: any = undefined
const data4: string[] = data3
function printDataIdx0(data: any) {
console.log(data[0])
}
大多数情况下
unknown
用于接受任意类型的变量实参,在函数内部只用于再次传递或输出结果,不获取属性或方法const data1: string[] = ['1']
const data2: unknown = data1
const data3: unknown = ['2']
const data4: string[] = data3 // error: Type 'unknown' is not assignable to type 'string[]'
function printDataIdx0(data: unknown) {
console.log(data[0]) //error: 'data' is of type 'unknown'
}
穷尽了data1
的所有可能类型,但没有匹配即为never
const data1: string & boolean = 1 // error: Type 'number' is not assignable to type 'never'
满足下述条件的数组即为元组:
const user1: [ string, number, string, string, string ] = [ '1', 20, 'shannxi xian', '13699999999', 'is user1' ]
在元组本身定义上结合数组的动态能力使用
const user1: [ string, number, string, ...any[] ] = [ '1', 20, 'shannxi xian', '13699999999', 'is user1' ]
const [ username, age, address, ...rest ]: [ string, number, string, ...any[] ] = [ '2', 20, 'shannxi xian', '13699999999', 'is user2' ]
因为元组类型无语义化,这个时候可以使用可变元组标签(tag)处理:
const [ username, age, address, ...rest ]: [ username: string, age: number, address: string, ...rest: any[] ] = [ '2', 20, 'shannxi xian', '13699999999', 'is user2' ]
- 合成类型
- 联合类型
|
(或关系) - 交叉类型
&
(与关系)
type UserName = { name: string }
type UserAge = { age: number }
const data1: UserName & UserAge = { name: '1', age: 1 }
const data2: UserName & string = { name: '1' } // error: Type '{ name: string; }' is not assignable to type 'string'.
const data3: string & boolean // const data3: never
- 字面量类型
type Num = 1 | 2 | 3
const data1: Num = 1
const data2: Num = 4 // error
// 常用于后台接口中的开关字段
type ToggleNumber = 0 | 1 // 大多数情况下使用 enum 代替
- 接口
- 接口继承
- 使用
class
实现接口 - 重名接口会自动合并属性签名
- 未知属性名定义
- 通过索引访问签名
一种定义对象的类型
接口定义对象的优点:
interface User {
name: string
age: number
}
interface UserWithRoles extends User {
roles: string[]
}
interface List {
add(): void
remove(): void
}
class ArrayList implements List {
add(): void {
// TODO
}
remove(): void {
// TODO
}
}
class LinkedList implements List {
add(): void {
// TODO
}
remove(): void {
// TODO
}
}
可索引签名:
interface User {
name: string
age: number
[key: string]: any
}
// 需要注意的是,可索引签名中的 [key: string],并不是指 key 的类型只能是 string
const user: User = {
name: '1',
age: 2,
[Symbol('user')]: 'user symbol',
1: 2,
true: 1,
[new Set('1')]: 1 // error: A computed property name must be of type 'string', 'number', 'symbol', or 'any'
}
const Id = Symbol('user')
interface User {
[Id]: string | number
name: string
age: number
}
type UserName = User['name']
type UnionUserNameWithAge = User['name' | 'age']
// 注意这里的 'name' 'age' 均为索引(指类型),可看下方示例
type UserUId = User[Id] // error: 'Id' refers to a value, but is being used as a type here
type UserId = User[typeof Id]
注意点
- 通过索引签名取值时需为不可变量
const user = { name: '1', age: 2 }
const userName = user['name']
const user1 = { name: '1', age: 2 }
let user1NameSign = 'name'
const user1Name = user[userNameSign] // error: No index signature with a parameter of type 'string' was found on type '{ name: string; age: number; }'
const user2 = { name: '1', age: 2 }
const user2NameSign = 'name'
const user2Name = user[user2NameSign]
关键字
typeof
获取变量的类型,也称类型守卫
类型范围:
”string” | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
对于 array | set | map | class 还是输出 Object,常见方案是使用
Object.prototype.toString.call()
来判断,但是 class 还是识别为 Object,需要使用下方 instanceof
instanceof
一般用于类或函数实例判断
is
常用于自定义类型守卫
class User {}
class UserA extends User {
actionA() {}
}
class UserB extends User {
actionB(user: UserA | UserB) {
if(isUserA(user)) {
user.actionB // get actionB
}
}
}
function isUserA(user: UserA | UserB /* user: any */): user is UserA {
return user instanceof UserA
}
as
类型断言,告诉编译器类型已确定无需检查
还可以使用类型转换达到相同目的,例如:
const data1 = 123
const data2 = <any>data1
keyof
获取类型中的 key/index,常用于对象、数组
interface User {
id: string | number
name: string
age: number
}
type UserKeys = keyof User // 此时 UserKeys 的类型只是 keyof User 而非 'id' | 'name' | 'age'
// 优化,本质在于使用 entends 后,ts 解析器会有递归判断操作,详情见下方:泛型-细节
// type Keys<T> = T extends any ? T : never
// type UserKeys = Keys<keyof User>
in
迭代类型,目前只接受 string(模版字符串)、number、symbol
interface User {
id: string | number
name: string
age: number
}
type UserData = {
[k in keyof User]: User[k]
}
// 相当于:
// type UserData = {
// ['id']: User['id']
// ['name']: User['name']
// ['age']: User['age']
// }
const
约束数组当中每个元素为只读状态
const numberRecords = [10, 20, 30] as const
numberRecords[0] = 100 // error: Cannot assign to '0' because it is a read-only property
infer
充当占位符,在使用(调用)时才会被推倒出来类型
type ElementOf<T> = T extends (infer E)[] ? E : never
type ArgumentsOf<T> = T extends (...args: infer A) => any ? A : never
type ReturnOf<T extends (...args: any[]) => any> =
ReturnType<T> extends Promise<infer R> ? R : ReturnType<T>
// vue3 core
function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
return isRef(ref) ? (ref.value as any): ref
// 不写 any 会报错:不能将 unknown 分配给 T extends Ref<infer V> ? V : T
// 因为 infer 只是占位符无具体类型,所以这个给 any,当使用 unref 时传入的类型会讲 infer 替换掉,此时 any 会被写为具体类型
}
declare
常用在声明文件(d.ts)中标明类型,见下方声明文件
内置类型
Extract
提取匹配的类型
Exclude
提取非匹配的类型
Record
描述对象,灵活的对键值做限制
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
// 需要注意的时虽然 ts 内部对 Record 的声明如上,且 key 的类型为 keyof any
// 但是当鼠标移入后显示 K extends string | number | symbol 这是 ts 内部所做的限制,记住就行
Pick
按照需求提取对象类型(支持 type、interface、class)
Omit
按照需求排除对象类型(支持 type、interface、class)
Reuqired
对象属性改为必填项目,注意源代码实现中的
-?
代表删除非必填Partial
对象属性改为非必填项,注意源代码实现中的
?
代表新增非必填类
基础
class People {
name: string
age: number
constructor(_name: string, _age: number) {
this.name = _name
this.age = _age
}
// 上述可简化为:
// constructor(public name: string, public age: number) {}
eat() {}
walk() {}
}
静态成员
class People {
static count: number
constructor(public name: string, public age: number) {
People.count ++
}
eat() {}
walk() {}
}
单例
静态成员在项目中一般用于单例模式
- 静态变量/方法实现
class DateUtil {
static formatDate() {}
static diffDateByHour() {}
static diffDateByDay() {}
}
console.log(DateUtil.formatDate)
export default DateUtil
- 立即创建单例模式
利用静态变量共享内存空间以及最先(立即)执行的特性,优点是使用时只会联想如下三个方法名不会将原型链方法暴漏出来
class DateUtil {
static dateUtil = new DateUtil()
private constructor(){} // 防止外部实例化,确保单例
formatDate() {}
diffDateByHour() {}
diffDateByDay() {}
}
console.log(DateUtil.dateUtil.formatDate)
const dateUtil1 = DateUtil.dateUtil
const dateUtil2 = DateUtil.dateUtil
console.log(dateUtil1 === dateUtil2) // true, 确定是单例
export const dateUtil = DateUtil.dateUtil
export default DateUtil
- 非立即创建单例模式
class DateUtil {
static dateUtil: DateUtil
static getInstance() {
if(!this.dateUtil) {
this.dateUtil = new DateUtil()
}
return this.dateUtil
}
private constructor(){} // 防止外部实例化,确保单例
formatDate() {}
diffDateByHour() {}
diffDateByDay() {}
}
console.log(DateUtil.getInstance().formatDate)
const dateUtil1 = DateUtil.getInstance()
const dateUtil2 = DateUtil.getInstance()
console.log(dateUtil1 === dateUtil2) // true, 确定是单例
export const dateUtil = DateUtil.getInstance()
export default DateUtil
条件类型
三目运算符、
extend
示例
type Data = string | number extends string | number ? string : never
泛型
定义
- 定义时不明确但在使用时可明确或可自动推导出的某种具体类型的数据类型
- 编译期间进行数据类型的检查
class<T> // 类、函数、属性、变量均可使用泛型
// T: 泛型形参类型(可类比于参数的概念,虽然可以赋默认值但均为“动态”变量即定义时不明确)。一般使用大些字母(A-Z)表示也可使用语义化的英文单词表示
type Ref<V> = { value: V }
const elRef: Ref<HTMLElement | null> = { el: null }
function ref<V>(value: V) {
return {
value
}
}
const elRef = ref<HTMLElement | null>(null)
应用
class ArrayList<T> {
constructor(public arr: T[]) {}
add(ele: T) {
this.arr.push(ele)
return ele
}
get(idx: number) {
return this.arr[idx]
}
}
// vue3 core
class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly __v_isRef = true
constructor(private readonly _object: T, private readonly _key: K) {}
get value() {
return this._object[this._key]
}
set value(newValue) {
this._object[this._key] = newValue
}
}
默认值与约束
// 默认值
T = any
V = Record<string, any>
// 约束
type PropKeys<T extends object> = keyof T
反向赋值(推断)
// vue3 core
class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly __v_isRef = true
constructor(private readonly _object: T, private readonly _key: K) {}
get value() {
return this._object[this._key]
}
set value(newValue) {
this._object[this._key] = newValue
}
}
const data = new ObjectRefImpl({ name: "i7eo", age: 29 }, "age") // 这里在实例化时没有显示传入泛型类型,ts 会反向推断
细节
泛型会根据传入的类型(联合类型等)迭代判断,如下:
type Data = string | number | boolean extends string | number ? string : never // type Data = never
// 这一步相当于 type Data1 = Extract<string | number | boolean, string | number>
type ConditionData<T> = T extends string | number ? T : never
type Data1 = ConditionData<string | number | boolean> // type Data1 = string | number
不使用泛型时,
string | number | boolean extends string | number
均为一次判断;使用泛型时 T extends string | number
typescript 解析器会迭代 T
进行多次判断,如:string
number
boolean
string | number
string | boolean
number | boolean
函数重载
定义
一组具有相同函数名,不同函数参数的和返回值无关并且具有一个实现签名和一个或多个重载签名的多个函数的类型
示例
enum MessageType {
image = 'Image',
audio = 'Audio'
}
interface Message {
id: number
type: MessageType
description: string
}
const messages: Message[] = [
{
id: 1,
type: MessageType.image,
description: 'message1'
},
{
id: 2,
type: MessageType.image,
description: 'message2'
},
{
id: 3,
type: MessageType.audio,
description: 'message3'
}
]
function searchMessage(condition: MessageType | number) {
if(typeof condition === 'number') {
return messages.find(msg => condition === msg.id)
}else{
return messages.filter(msg => condition === msg.type)
}
}
searchMessage(1)?.id // error: Property 'id' does not exist on type 'Message[]'
(searchMessage(2) as Message).type // 必须使用断言才可以获取 Message 属性
改进
enum MessageType {
image = 'Image',
audio = 'Audio'
}
interface Message {
id: number
type: MessageType
description: string
}
const messages: Message[] = [
{
id: 1,
type: MessageType.image,
description: 'message1'
},
{
id: 2,
type: MessageType.image,
description: 'message2'
},
{
id: 3,
type: MessageType.audio,
description: 'message3'
}
]
// 函数重载签名(overload signature)
function searchMessage(condition: number): Message | undefined
function searchMessage(condition: MessageType): Message[]
// 函数实现签名(implementation signature)
function searchMessage(condition: number | MessageType): Message | undefined | Message[] {
if(typeof condition === 'number') {
return messages.find(msg => condition === msg.id)
}else{
return messages.filter(msg => condition === msg.type)
}
}
// 函数调用时会首先查找重载签名,然后调用函数签名,最终执行函数
searchMessage(1)?.id
searchMessage(MessageType.image).map(msg => ({...msg}))
泛型函数
function quickSort<T>(arr: T[]) : T[] {
// ...
}
声明文件
实现
declare let/const // 声明全局变量
declare function // 声明全局方法
declare class // 声明全局类
declare enum // 声明全局枚举类型
declare namespace // 声明(含有子属性)全局对象
interface/type // 声明全局类型,不需要 declare
// 需要注意的是在 d.ts 文件中多个 declare 作用于统一目标是被允许的类比函数重载,但是多个 export 则不行
作用域
ts 会自动搜索 include 或根目录(tsconfig.json 所在位置)下的 d.ts 声明文件
命名空间
// index.d.ts
declare namespace JQuery {
type CSSSelector = {
css: (key: string, value: string) => CSSSelector
}
export function $(ready: (...args:any[]) => void): void
export function $(selector: any): CSSSelector
export namespace $ {
function ajax(url: string, settings?: any): void
function get(url: string, settings?: any): void
function post(url: string, settings?: any): void
}
}
// index.ts
JQuery.$.ajax("")
模块声明
命名空间使用时需要手动写出空间名称比较繁琐,改进方式是使用模块声明
// index.d.ts
declare modul JQuery {
type CSSSelector = {
css: (key: string, value: string) => CSSSelector
}
function $(ready: (...args:any[]) => void): void
function $(selector: any): CSSSelector
namespace $ {
function ajax(url: string, settings?: any): void
function get(url: string, settings?: any): void
function post(url: string, settings?: any): void
}
export default $ // 或兼容 commonjs export = $
}
tsconfig.json
作用域范围
在项目根目录下放置那么作用域为整个项目,在某个文件夹下放置那么作用域为当前文件夹。重复放置的话遵循就近原则
extends
[email protected]
~[email protected]
只能使用单继承配置[email protected]
5.0之后可以使用多继承的方式,extends: []
数组接收多配置,优先级由左至右升高,可参考:compilerOptions
lib
编译时使用的 es 版本以及环境所需要的必要依赖,例如 web 项目配置:
lib: [”esnext”, “DOM”]
需要注意的是 ts 本身的编译工作环境为本机 node 环境下,所以 node 项目除了标明 es 版本即可无需多余配置module
ts 编译后使用哪种模块规范,即一个模块是如何声明使用导入/导出的语法,例如:cjs ⇒ commonjs,esm ⇒ esnext
moduleResolution
isolatedModules
是否将每个文件都视作模块
esModuleInterop
抹平 CommonJS/AMD/UMD 与 ESM 的差异,将 CommonJS/AMD/UMD 这些模块/文件按照 ESM 的行为解析
allowSyntheticDefaultImports
如果模块/文件中没有默认导出,将该项设置为 true 后编译器会自动合成(生成)默认导出
Default:true
ifesModuleInterop
is enabled,module
issystem
, ormoduleResolution
isbundler
;false
otherwise.