Typescript 拾遗

date
May 20, 2024
slug
relearn-typescript
status
Published
tags
Typescript
summary
Typescript 知识点总结
type
Post
💡
下文中的“签名”可以理解为“定义“或”声明“

基础

类型注解

const data: number = 3

类型推导

const data = 3

常用类型

  1. 基本类型
    1. 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}`
    2. null
      1. 参考下方 undefined
    3. undefined
      1. // 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
  1. 根类型
    1. 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 类型简写
  1. 对象类型
    1. Array/object/function
      const data1: object = 1 // error
      const data2: object = { name: '1' }
  1. 枚举
    1. 数字枚举
      1. enum Roles {
        	owner,
        	maintainer,
        	developer
        }
        
        Roles['owner'] // 1
        Roles[1] // 'maintainer'
        
        // 默认值为 number 类型可反取,为 string 类型不支持反取
    2. 字符串枚举
      1. 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'
  1. 其他类型
    1. any/unknown/never/void/元组(tuple)/可变元组
      下列描述中的“父类“指等号左侧变量,”子类“指等号右侧变量
    2. any 可以是任何类型的”父类“或”子类“,可以从中获取任意名称的属性与方法
      1. 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])
        }
    3. unknown 可以是任何类型的”父类“但不是”子类“,不能从中获取任意名称的属性与方法
      1. 💡
        大多数情况下 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'
        }
    4. never
      1. 穷尽了 data1 的所有可能类型,但没有匹配即为 never
        const data1: string & boolean = 1 // error: Type 'number' is not assignable to type 'never'
    5. void
    6. tuple
      1. 满足下述条件的数组即为元组:
        1. 在定义时每个元素类型都确定
        1. 元素值的数据类型必须是当前元素定义的类型
        1. 元素值个数与定义时个数相同
        const user1: [ string, number, string, string, string ] = [ '1', 20, 'shannxi xian', '13699999999', 'is user1' ]
    7. 可变 tuple
      1. 在元组本身定义上结合数组的动态能力使用
        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' ]
  1. 合成类型
    1. 联合类型 | (或关系)
    2. 交叉类型 & (与关系)
      1. 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
  1. 字面量类型
    1. type Num = 1 | 2 | 3
      
      const data1: Num = 1
      const data2: Num = 4 // error
      
      
      // 常用于后台接口中的开关字段
      type ToggleNumber = 0 | 1 // 大多数情况下使用 enum 代替 
  1. 接口
    1. 一种定义对象的类型
      接口定义对象的优点:
    2. 接口继承
      1. interface User {
        	name: string
        	age: number
        }
        
        interface UserWithRoles extends User {
        	roles: string[]
        }
    3. 使用 class 实现接口
      1. 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 
        	}
        }
    4. 重名接口会自动合并属性签名
    5. 可索引签名:
    6. 未知属性名定义
      1. 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'
        }
    7. 通过索引访问签名
      1. 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]

注意点

  1. 通过索引签名取值时需为不可变量
    1. 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() {}
}

单例

静态成员在项目中一般用于单例模式
  1. 静态变量/方法实现
    1. class DateUtil {
      	static formatDate() {}
      	static diffDateByHour() {}
      	static diffDateByDay() {}
      }
      
      console.log(DateUtil.formatDate)
      
      export default DateUtil
  1. 立即创建单例模式
    1. 💡
      利用静态变量共享内存空间以及最先(立即)执行的特性,优点是使用时只会联想如下三个方法名不会将原型链方法暴漏出来
      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
  1. 非立即创建单例模式
    1. 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

泛型

定义

  1. 定义时不明确但在使用时可明确或可自动推导出的某种具体类型的数据类型
  1. 编译期间进行数据类型的检查
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

  1. lib
    1. 编译时使用的 es 版本以及环境所需要的必要依赖,例如 web 项目配置:lib: [”esnext”, “DOM”]需要注意的是 ts 本身的编译工作环境为本机 node 环境下,所以 node 项目除了标明 es 版本即可无需多余配置
  1. module
    1. ts 编译后使用哪种模块规范,即一个模块是如何声明使用导入/导出的语法,例如:cjs ⇒ commonjs,esm ⇒ esnext
  1. moduleResolution
    1. 告诉 ts 如何解析模块,即一个模块包括相对路径以及非相对路径(也就是第三方库,亦或者说 npm 包)是按照怎样的规则去查找的
      Default: Classic if module is AMDUMDSystem, or ES6/ES2015; Matches if module is node16 or nodenextNode otherwise.
      详情可参考:
  1. isolatedModules
    1. 是否将每个文件都视作模块
  1. esModuleInterop
    1. 抹平 CommonJS/AMD/UMD 与 ESM 的差异,将 CommonJS/AMD/UMD 这些模块/文件按照 ESM 的行为解析
  1. allowSyntheticDefaultImports
    1. 如果模块/文件中没有默认导出,将该项设置为 true 后编译器会自动合成(生成)默认导出
      Default: true if esModuleInterop is enabled, module is system, or moduleResolution is bundlerfalse otherwise.

参考

 

© i7eo 2017 - 2024