TypeScript 深度强化第二天:从类型创建类型的高级技术
掌握 TypeScript 类型系统的核心能力:泛型、类型运算符与高级类型转换
🎯 学习目标
通过第二天的学习,你将:
- 深度理解泛型的设计思想和应用场景
- 掌握 keyof、typeof 等核心类型运算符
- 学会使用条件类型进行类型推导
- 熟练运用映射类型和模板字面量类型
- 构建可复用的高级类型工具
第 1 章:泛型编程的艺术
泛型的本质与价值
什么是泛型?
泛型就像是一个"万能盒子",你不知道里面装的是什么,但是你知道无论装什么,盒子的使用方法都是一样的。比如说,你有一个函数要处理数据,但你不知道这个数据是字符串、数字还是对象,泛型就能帮你写一个通用的处理函数。
为什么需要泛型?
想象一下,如果没有泛型,你要写一个返回输入值的函数:
- 处理字符串:
function returnString(input: string): string
- 处理数字:
function returnNumber(input: number): number
- 处理布尔值:
function returnBoolean(input: boolean): boolean
这样要写无数个函数!泛型就是为了解决这个重复代码的问题。
核心概念深度理解:
- 类型参数化:就像函数可以接收参数一样,泛型让类型也能接收"参数"
- 类型安全:虽然是通用的,但 TypeScript 仍然知道具体的类型,不会出错
- 代码复用:一套代码,多种类型都能用
- 延迟类型确定:在定义时不确定类型,在使用时才确定
泛型的工作原理:
- 定义时:用
<T>
表示"这里有个类型,但现在不知道是什么" - 使用时:TypeScript 会根据你传入的值自动推断类型
- 编译时:TypeScript 会检查类型是否匹配,确保安全
// 最基础的泛型函数 - 就像一个回音壁,你说什么它返回什么
function identity<T>(arg: T): T {
return arg
}
泛型的类型推断:
TypeScript 非常聪明,大多数情况下能自动推断出泛型的具体类型,你不需要手动指定:
// TypeScript会自动推断类型,非常智能
let result1 = identity('hello world') // TypeScript知道这是string
let result2 = identity(42) // TypeScript知道这是number
let result3 = identity(true) // TypeScript知道这是boolean
// 你也可以明确指定类型,有时候TypeScript推断不出来
let result4 = identity<string>('explicit type')
let result5 = identity<number[]>([1, 2, 3, 4])
泛型的类型传递:
泛型最强大的地方是类型信息会在整个调用链中传递,确保类型安全:
// 泛型的强大之处:类型信息会传递
function getArrayLength<T>(arr: T[]): number {
return arr.length // TypeScript知道arr是数组,所以有length属性
}
let stringArrayLength = getArrayLength(['a', 'b', 'c']) // 返回3
let numberArrayLength = getArrayLength([1, 2, 3, 4, 5]) // 返回5
泛型与 keyof 的结合:
这是一个更高级的例子,展示了泛型如何与其他 TypeScript 特性结合使用:
// 更复杂的例子:处理对象
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key] // 这里TypeScript知道返回值的确切类型
}
const person = {
name: 'John',
age: 30,
isStudent: false,
}
const personName = getProperty(person, 'name') // TypeScript知道这是string
const personAge = getProperty(person, 'age') // TypeScript知道这是number
// const invalid = getProperty(person, "height"); // 编译错误!person没有height属性
泛型约束的深度应用
什么是泛型约束?
泛型约束就像是给"万能盒子"加了一些限制条件。比如说,这个盒子只能装"有轮子的东西",那汽车、自行车可以装,但是石头就不行。
为什么需要约束?
有时候你的泛型函数需要使用某些特定的属性或方法,但是如果不加约束,TypeScript 不知道这个类型有没有这些属性,就会报错。
约束的几种常见形式:
- 基础约束:要求类型必须有某些属性
- keyof 约束:要求类型必须是某个对象的键名
- 条件约束:根据条件动态约束
- 多重约束:同时满足多个条件
1. 基础约束:要求类型具有特定属性
最简单的约束是要求泛型类型必须具有某些属性:
// 1. 基础约束:要求类型必须有length属性
interface HasLength {
length: number
}
// 这个函数只接受有length属性的类型
function logLength<T extends HasLength>(arg: T): T {
console.log(`Length is: ${arg.length}`) // 现在可以安全使用length了
return arg
}
logLength('hello') // ✅ 字符串有length
logLength([1, 2, 3]) // ✅ 数组有length
logLength({ length: 10, value: 'test' }) // ✅ 对象有length
// logLength(123); // ❌ 数字没有length,编译错误
2. keyof 约束:确保属性名存在
这种约束确保我们只能访问对象中真实存在的属性:
// 2. keyof约束:确保键名存在于对象中
function getObjectProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = {
username: 'alice',
age: 25,
email: 'alice@example.com',
isActive: true,
}
const username = getObjectProperty(user, 'username') // string类型
const age = getObjectProperty(user, 'age') // number类型
// const invalid = getObjectProperty(user, "height"); // 编译错误!
3. 多重约束:同时满足多个条件
有时候我们需要类型同时满足多个条件,可以使用交叉类型(&):
// 3. 多重约束:同时满足多个条件
interface Serializable {
serialize(): string
}
interface Timestamped {
timestamp: Date
}
// 要求类型同时实现两个接口
function processData<T extends Serializable & Timestamped>(data: T): string {
const serialized = data.serialize() // 可以调用serialize方法
const time = data.timestamp.toISOString() // 可以访问timestamp属性
return `${serialized} at ${time}`
}
4. 条件约束:根据类型特征进行约束
使用条件类型可以创建更复杂的约束逻辑:
// 4. 条件约束:根据类型特征进行约束
type ArrayElement<T> = T extends (infer U)[] ? U : never
type FunctionReturn<T> = T extends (...args: any[]) => infer R ? R : never
type StringArrayElement = ArrayElement<string[]> // string
type NumberArrayElement = ArrayElement<number[]> // number
type FuncReturnType = FunctionReturn<() => boolean> // boolean
5. 实际应用:类型安全的事件系统
让我们看一个完整的实际应用,展示泛型约束的强大威力:
// 5. 实际应用:创建类型安全的事件系统
interface EventMap {
click: { x: number; y: number }
keypress: { key: string; ctrl: boolean }
resize: { width: number; height: number }
}
class EventEmitter<T extends Record<string, any>> {
private listeners: { [K in keyof T]?: Array<(data: T[K]) => void> } = {}
on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
if (!this.listeners[event]) {
this.listeners[event] = []
}
this.listeners[event]!.push(callback)
}
emit<K extends keyof T>(event: K, data: T[K]): void {
const callbacks = this.listeners[event]
if (callbacks) {
callbacks.forEach(callback => callback(data))
}
}
}
事件系统的使用示例:
这个事件系统确保了类型安全,你不能传入错误的事件数据:
const emitter = new EventEmitter<EventMap>()
// 类型安全的事件监听
emitter.on('click', data => {
console.log(`Clicked at ${data.x}, ${data.y}`) // data自动推断为{x: number, y: number}
})
emitter.on('keypress', data => {
console.log(`Key pressed: ${data.key}`) // data自动推断为{key: string, ctrl: boolean}
})
// 类型安全的事件触发
emitter.emit('click', { x: 100, y: 200 }) // ✅ 正确
emitter.emit('keypress', { key: 'Enter', ctrl: false }) // ✅ 正确
// emitter.emit("click", { key: "Enter" }); // ❌ 编译错误,类型不匹配
泛型类与泛型接口的实际应用
什么是泛型类?简单来说:
泛型类就像是一个"模板工厂",你可以用同一个模板生产不同类型的产品。比如你有一个数据存储的类,可以存储字符串、数字、对象等任何类型的数据。
泛型类的核心特点:
- 类的属性和方法可以使用类型参数
- 静态成员不能使用类的类型参数(这是重要限制!)
- 可以继承泛型类,也可以实现泛型接口
- 实例化时需要指定具体类型
泛型接口的作用:
- 定义通用的契约结构
- 可以描述函数类型、对象类型、索引类型
- 支持多个类型参数
- 可以有默认类型参数
泛型数据容器类的实现:
让我们先实现一个通用的数据容器类,它可以存储任何类型的数据:
// 泛型数据容器类 - 像一个智能仓库
class DataContainer<T> {
private items: T[] = []
// 添加数据
add(item: T): void {
this.items.push(item)
}
// 获取数据
get(index: number): T | undefined {
return this.items[index]
}
// 查找数据 - 这里展示了泛型约束的使用
find<K extends keyof T>(property: K, value: T[K]): T[] {
return this.items.filter(item => item[property] === value)
}
// 获取所有数据
getAll(): readonly T[] {
return [...this.items] // 返回副本,保护内部数据
}
// 获取数量
get count(): number {
return this.items.length
}
// 清空数据
clear(): void {
this.items = []
}
}
使用数据容器的实际例子:
现在让我们看看如何在实际项目中使用这个数据容器:
// 使用示例1:存储用户信息
interface User {
id: number
name: string
email: string
isActive: boolean
}
const userContainer = new DataContainer<User>()
userContainer.add({ id: 1, name: 'Alice', email: 'alice@example.com', isActive: true })
userContainer.add({ id: 2, name: 'Bob', email: 'bob@example.com', isActive: false })
// 类型安全的查找
const activeUsers = userContainer.find('isActive', true) // 返回User[]
const userById = userContainer.find('id', 1) // 返回User[]
// 使用示例2:存储产品信息
interface Product {
sku: string
name: string
price: number
category: string
}
const productContainer = new DataContainer<Product>()
productContainer.add({ sku: 'P001', name: 'Laptop', price: 999, category: 'Electronics' })
泛型接口的设计:
接下来我们定义一些通用的接口,用于 API 响应格式:
// 泛型接口:API响应格式
interface ApiResponse<TData, TError = string> {
success: boolean
data?: TData
error?: TError
message: string
timestamp: number
}
// 分页响应接口
interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number
limit: number
total: number
totalPages: number
}
}
// 实际使用
type UserListResponse = PaginatedResponse<User>
type ProductResponse = ApiResponse<Product>
type ErrorResponse = ApiResponse<null, { code: number; details: string }>
泛型工厂函数:
工厂函数可以帮助我们快速创建符合规范的响应对象:
// 泛型工厂函数
function createSuccessResponse<T>(data: T): ApiResponse<T> {
return {
success: true,
data,
message: 'Operation successful',
timestamp: Date.now(),
}
}
function createErrorResponse<T = null>(error: string): ApiResponse<T> {
return {
success: false,
error,
message: 'Operation failed',
timestamp: Date.now(),
}
}
// 使用工厂函数
const userResponse = createSuccessResponse({ id: 1, name: 'Alice', email: 'alice@example.com', isActive: true })
const errorResponse = createErrorResponse('User not found')
高级泛型类:缓存管理器
让我们实现一个更复杂的泛型类,展示多个泛型参数的使用:
// 高级泛型类:缓存管理器
class CacheManager<TKey extends string | number, TValue> {
private cache = new Map<TKey, { value: TValue; expiry: number }>()
private defaultTTL: number
constructor(defaultTTL: number = 300000) {
// 默认5分钟
this.defaultTTL = defaultTTL
}
set(key: TKey, value: TValue, ttl?: number): void {
const expiry = Date.now() + (ttl || this.defaultTTL)
this.cache.set(key, { value, expiry })
}
get(key: TKey): TValue | null {
const item = this.cache.get(key)
if (!item) return null
if (Date.now() > item.expiry) {
this.cache.delete(key)
return null
}
return item.value
}
has(key: TKey): boolean {
return this.get(key) !== null
}
delete(key: TKey): boolean {
return this.cache.delete(key)
}
clear(): void {
this.cache.clear()
}
// 清理过期项
cleanup(): number {
const now = Date.now()
let cleaned = 0
for (const [key, item] of this.cache.entries()) {
if (now > item.expiry) {
this.cache.delete(key)
cleaned++
}
}
return cleaned
}
}
缓存管理器的使用:
这个缓存管理器可以用于不同类型的数据缓存:
// 使用缓存管理器
const userCache = new CacheManager<number, User>()
const productCache = new CacheManager<string, Product>()
userCache.set(1, { id: 1, name: 'Alice', email: 'alice@example.com', isActive: true })
productCache.set('P001', { sku: 'P001', name: 'Laptop', price: 999, category: 'Electronics' })
const cachedUser = userCache.get(1) // User | null
const cachedProduct = productCache.get('P001') // Product | null
泛型类与泛型接口
泛型不仅可以应用于函数,还可以应用于类和接口,提供强大的类型复用能力。
设计原则:
- 泛型类的静态成员不能使用类的类型参数
- 泛型接口可以描述函数类型、索引类型等
- 泛型约束在类和接口中同样适用
- 继承关系中的泛型传递规则
// 泛型数据存储类
class DataStorage<DataType> {
private dataList: DataType[] = []
add(item: DataType): void {
this.dataList.push(item)
}
get(index: number): DataType | undefined {
return this.dataList[index]
}
find<KeyName extends keyof DataType>(key: KeyName, value: DataType[KeyName]): DataType[] {
return this.dataList.filter(item => item[key] === value)
}
getAll(): readonly DataType[] {
return [...this.dataList]
}
}
// 使用示例
interface ProductInfo {
productId: string
productName: string
price: number
category: string
}
const productStorage = new DataStorage<ProductInfo>()
productStorage.add({
productId: 'P001',
productName: 'Smartphone',
price: 2999,
category: 'Electronics',
})
const electronics = productStorage.find('category', 'Electronics')
// 泛型接口:API响应类型
interface ApiResponse<DataType> {
success: boolean
message: string
data?: DataType
errorCode?: number
timestamp: number
}
interface PaginatedResponse<ItemType> extends ApiResponse<ItemType[]> {
pagination: {
currentPage: number
pageSize: number
total: number
totalPages: number
}
}
// 工厂函数模式
function createApiResponse<T>(data: T): ApiResponse<T> {
return {
success: true,
message: 'Operation successful',
data,
timestamp: Date.now(),
}
}
const userResponse = createApiResponse({ userId: 1, username: 'admin' })
第 2 章:keyof 类型运算符的威力
keyof 运算符基础 - 获取对象所有键名
什么是 keyof?
keyof 就像是一个"钥匙串提取器",给它一个对象类型,它就能把这个对象所有的属性名(键名)提取出来,组成一个联合类型。
想象你有一个房子,keyof 就能告诉你这个房子有哪些房间的钥匙。
为什么 keyof 这么重要?
- 类型安全:确保你只能访问对象真实存在的属性
- 动态属性访问:可以根据属性名动态获取属性值
- 工具类型基础:很多高级类型都是基于 keyof 构建的
- 重构友好:当对象结构改变时,相关类型会自动更新
keyof 的工作原理:
interface Person {
name: string
age: number
email: string
}
// keyof Person 等于 "name" | "age" | "email"
type PersonKeys = keyof Person
深入理解 keyof 的特性:
- 基础用法:提取对象类型的所有键名
- 索引签名处理:对于索引签名,返回索引的类型
- 数字键的特殊处理:JavaScript 中数字键会被转换为字符串
- Symbol 键的支持:也可以提取 Symbol 类型的键
- 联合类型的分布式特性:对联合类型使用 keyof 会有特殊行为
1. 基础 keyof 使用:
让我们从一个简单的用户接口开始,看看 keyof 是如何工作的:
// 1. 基础keyof使用
interface User {
id: number
username: string
email: string
isActive: boolean
profile: {
firstName: string
lastName: string
}
}
type UserKeys = keyof User // "id" | "username" | "email" | "isActive" | "profile"
类型安全的属性访问:
有了 keyof,我们可以创建类型安全的属性访问函数:
// 实际应用:类型安全的属性访问函数
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user: User = {
id: 1,
username: 'john_doe',
email: 'john@example.com',
isActive: true,
profile: { firstName: 'John', lastName: 'Doe' },
}
const userId = getProperty(user, 'id') // number类型
const username = getProperty(user, 'username') // string类型
const profile = getProperty(user, 'profile') // {firstName: string, lastName: string}类型
// const invalid = getProperty(user, "nonexistent"); // 编译错误!
2. 索引签名的 keyof 行为:
当接口有索引签名时,keyof 的行为会有所不同:
// 2. 索引签名的keyof行为
interface StringDictionary {
[key: string]: any
}
interface NumberDictionary {
[key: number]: any
}
type StringDictKeys = keyof StringDictionary // string | number
type NumberDictKeys = keyof NumberDictionary // number
// 为什么StringDictionary的keyof包含number?
// 因为JavaScript中 obj[1] 等价于 obj["1"]
3. 混合索引签名的处理:
当接口既有具体属性又有索引签名时,keyof 会如何处理:
// 3. 混合索引签名
interface MixedInterface {
name: string
[key: string]: any
}
type MixedKeys = keyof MixedInterface // string | number
// 这里包含了"name",但由于索引签名的存在,结果是string | number
4. 数字键的特殊处理:
JavaScript 中的数字键会被转换为字符串,keyof 也遵循这个规则:
// 4. 数字键的特殊处理
interface ArrayLike {
0: string
1: number
2: boolean
length: number
}
type ArrayLikeKeys = keyof ArrayLike // "0" | "1" | "2" | "length"
// 注意:数字键被转换为字符串字面量类型
5. Symbol 键的支持:
keyof 也支持 Symbol 类型的键:
// 5. Symbol键的支持
const symbolKey = Symbol('mySymbol')
interface WithSymbol {
name: string
[symbolKey]: number
}
type WithSymbolKeys = keyof WithSymbol // "name" | typeof symbolKey
6. 联合类型与交叉类型的 keyof:
这是一个重要概念,联合类型和交叉类型的 keyof 行为不同:
// 6. 联合类型的keyof - 重要概念!
interface Cat {
name: string
meow(): void
}
interface Dog {
name: string
bark(): void
}
// 联合类型的keyof只包含共同的键
type AnimalKeys = keyof (Cat | Dog) // "name"
// 因为只有name是Cat和Dog都有的属性
// 7. 交叉类型的keyof
type CatDogKeys = keyof (Cat & Dog) // "name" | "meow" | "bark"
// 交叉类型包含所有属性
8. 实际应用:对象更新函数:
让我们看看如何在实际项目中使用 keyof 创建实用的工具函数:
// 8. 实际应用:创建类型安全的对象更新函数
function updateObject<T>(obj: T, updates: Partial<T>): T {
return { ...obj, ...updates }
}
// 更高级的版本:只允许更新特定字段
function updateSpecificFields<T, K extends keyof T>(obj: T, fields: K[], updates: Pick<T, K>): T {
const result = { ...obj }
fields.forEach(field => {
if (field in updates) {
result[field] = updates[field]
}
})
return result
}
// 使用示例
const updatedUser = updateSpecificFields(
user,
['username', 'email'], // 只能更新这些字段
{ username: 'new_username', email: 'new@example.com' },
)
9. keyof 与泛型约束的结合:
这种模式在创建工具函数时非常有用:
// 9. keyof与泛型约束的结合
function createPropertyGetter<T>() {
return function <K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
}
const getUserProperty = createPropertyGetter<User>()
const userEmail = getUserProperty(user, 'email') // string类型
10. 条件类型中的 keyof 应用:
最后,让我们看看 keyof 在条件类型中的高级应用:
// 10. 条件类型中的keyof应用
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T]
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K
}[keyof T]
interface ExampleObject {
name: string
age: number
greet(): void
calculate(x: number): number
}
type FunctionProps = FunctionPropertyNames<ExampleObject> // "greet" | "calculate"
type NonFunctionProps = NonFunctionPropertyNames<ExampleObject> // "name" | "age"
高级 keyof 应用技巧
keyof 在实际开发中的常见模式:
- 属性选择器模式:创建类型安全的属性选择器
- 对象验证模式:验证对象是否包含必需属性
- 动态表单模式:根据对象结构动态生成表单
- 序列化模式:类型安全的对象序列化/反序列化
// 1. 属性选择器模式
class PropertySelector<T> {
constructor(private obj: T) {}
select<K extends keyof T>(...keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>
keys.forEach(key => {
result[key] = this.obj[key]
})
return result
}
exclude<K extends keyof T>(...keys: K[]): Omit<T, K> {
const result = { ...this.obj }
keys.forEach(key => {
delete result[key]
})
return result
}
}
// 使用示例
const selector = new PropertySelector(user)
const basicInfo = selector.select('id', 'username') // { id: number; username: string }
const withoutProfile = selector.exclude('profile') // 去掉profile的User类型
// 2. 对象验证模式
function validateRequiredFields<T, K extends keyof T>(obj: Partial<T>, requiredFields: K[]): obj is Pick<T, K> & Partial<T> {
return requiredFields.every(field => field in obj && obj[field] !== undefined)
}
// 使用示例
const partialUser: Partial<User> = { username: 'test' }
if (validateRequiredFields(partialUser, ['id', 'username'])) {
// 在这个if块中,TypeScript知道partialUser一定有id和username
console.log(partialUser.id) // 类型安全
console.log(partialUser.username) // 类型安全
}
// 3. 动态表单字段生成
type FormField<T, K extends keyof T> = {
key: K
label: string
type: T[K] extends string ? 'text' : T[K] extends number ? 'number' : T[K] extends boolean ? 'checkbox' : 'unknown'
required: boolean
}
function createFormFields<T>(): <K extends keyof T>(configs: { [P in K]: { label: string; required: boolean } }) => FormField<T, K>[] {
return function <K extends keyof T>(configs) {
return (Object.keys(configs) as K[]).map(key => ({
key,
label: configs[key].label,
type: 'text' as any, // 简化示例
required: configs[key].required,
}))
}
}
// 使用示例
const createUserFormFields = createFormFields<User>()
const formFields = createUserFormFields({
username: { label: '用户名', required: true },
email: { label: '邮箱', required: true },
isActive: { label: '是否激活', required: false },
})
// 4. 类型安全的深度路径访问
type DeepKeys<T> = T extends object
? {
[K in keyof T]: K extends string | number ? (T[K] extends object ? `${K}` | `${K}.${DeepKeys<T[K]>}` : `${K}`) : never
}[keyof T]
: never
type UserDeepKeys = DeepKeys<User> // "id" | "username" | "email" | "isActive" | "profile" | "profile.firstName" | "profile.lastName"
function getDeepProperty<T, K extends DeepKeys<T>>(obj: T, path: K): any {
const keys = path.split('.')
let result: any = obj
for (const key of keys) {
result = result[key]
if (result === undefined) break
}
return result
}
// 使用示例
const firstName = getDeepProperty(user, 'profile.firstName') // 类型安全的深度访问
// const invalid = getDeepProperty(user, "profile.age"); // 编译错误!
- keyof 在数组和元组上的特殊行为
// 基础keyof使用
interface EmployeeInfo {
employeeId: string
name: string
department: string
salary: number
hireDate: Date
}
type EmployeeFieldNames = keyof EmployeeInfo
// "employeeId" | "name" | "department" | "salary" | "hireDate"
// 索引签名的keyof
interface DynamicConfig {
[configName: string]: any
}
type ConfigKeyType = keyof DynamicConfig // string | number
interface ArrayIndexType {
[index: number]: string
}
type ArrayKeyType = keyof ArrayIndexType // number
// 联合类型的keyof
type TypeA = { a: string; commonField: boolean }
type TypeB = { b: string; commonField: boolean }
type CommonKeys = keyof (TypeA | TypeB) // "commonField"
// 交叉类型的keyof
type AllKeys = keyof (TypeA & TypeB) // "a" | "b" | "commonField"
keyof 在类型安全中的应用
keyof 的主要价值在于提供类型安全的属性访问,防止运行时错误。
应用场景:
- 动态属性访问的类型检查
- 对象属性的批量操作
- 配置对象的类型安全
- API 字段验证和转换
// 类型安全的属性获取器
function safeGetProperty<ObjectType, KeyName extends keyof ObjectType>(object: ObjectType, keyName: KeyName): ObjectType[KeyName] {
return object[keyName]
}
// 批量属性选择器
function selectProperties<ObjectType, KeyName extends keyof ObjectType>(object: ObjectType, keyList: KeyName[]): Pick<ObjectType, KeyName> {
const result = {} as Pick<ObjectType, KeyName>
keyList.forEach(key => {
result[key] = object[key]
})
return result
}
// 属性验证器
function validateRequiredProperties<ObjectType>(object: Partial<ObjectType>, requiredFields: (keyof ObjectType)[]): object is ObjectType {
return requiredFields.every(field => object[field] !== undefined)
}
// 使用示例
const productInfo = {
productId: 'SP001',
productName: 'Premium Headphones',
price: 599,
stock: 50,
description: 'High-quality audio equipment',
}
const productName = safeGetProperty(productInfo, 'productName') // string
const basicInfo = selectProperties(productInfo, ['productId', 'productName', 'price'])
// 表单数据处理
interface UserForm {
username: string
email: string
password: string
confirmPassword: string
agreeToTerms: boolean
}
function handleFormSubmission<FormType>(formData: Partial<FormType>, requiredFields: (keyof FormType)[]): { valid: boolean; error?: string; data?: FormType } {
if (!validateRequiredProperties(formData, requiredFields)) {
return { valid: false, error: 'Required fields are incomplete' }
}
return { valid: true, data: formData }
}
const formResult = handleFormSubmission({ username: 'test', email: 'test@example.com' }, ['username', 'email', 'password'])
keyof 与映射类型的结合
keyof 与映射类型结合使用,能够创建强大的类型转换工具。
核心技术:
- 属性遍历与类型映射
- 条件属性过滤
- 属性名称转换
- 值类型变换
// 创建可选版本
type MakeOptional<T> = {
[K in keyof T]?: T[K]
}
// 创建只读版本
type MakeReadonly<T> = {
readonly [K in keyof T]: T[K]
}
// 移除只读修饰符
type MakeWritable<T> = {
-readonly [K in keyof T]: T[K]
}
// 移除可选修饰符
type MakeRequired<T> = {
[K in keyof T]-?: T[K]
}
// 属性类型转换
type Stringify<T> = {
[K in keyof T]: string
}
// 条件属性过滤
type FilterFunctionProperties<T> = {
[K in keyof T]: T[K] extends Function ? never : T[K]
}
// 实际应用示例
interface DatabaseModel {
readonly id: number
name: string
description?: string
createdAt: Date
updatedAt: Date
deleteMethod(): void
saveMethod(): Promise<void>
}
type CreateInput = MakeOptional<Omit<DatabaseModel, 'id' | 'createdAt' | 'updatedAt' | 'deleteMethod' | 'saveMethod'>>
type UpdateInput = Partial<Pick<DatabaseModel, 'name' | 'description'>>
type QueryResult = MakeReadonly<FilterFunctionProperties<DatabaseModel>>
// 高级属性映射
type PropertyGetters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
type PropertySetters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void
}
type CompleteAccessors<T> = PropertyGetters<T> & PropertySetters<T>
type UserAccessors = CompleteAccessors<Pick<EmployeeInfo, 'name' | 'department'>>
// 生成: getName, setName, getDepartment, setDepartment 方法
第 3 章:typeof 类型运算符 - 从值到类型的桥梁
typeof 运算符的双重身份
什么是 typeof?
typeof 在 TypeScript 中有两个身份,就像一个人有两份工作:
- 运行时工作:检查 JavaScript 值的类型(这是 JavaScript 原生功能)
- 编译时工作:提取 TypeScript 类型信息(这是 TypeScript 特有功能)
简单来说,typeof 可以把一个具体的值"反向工程"成类型定义。
为什么 typeof 这么有用?
- 避免重复定义:有了值,就不需要再单独写类型定义
- 保持同步:值改变时,类型自动跟着变
- 提取复杂类型:从复杂对象或函数中提取类型
- 库类型提取:从第三方库的值中提取类型
typeof 的工作原理:
- 在类型位置使用时,typeof 会提取值的类型
- 在表达式位置使用时,typeof 执行运行时类型检查
- 只能用于标识符或其属性访问
- 与其他类型运算符组合使用能力强大
从配置对象提取类型:
这是 typeof 最常见的用法,从已有的配置对象中提取类型定义:
// 基础typeof使用
const configObject = {
database: {
host: 'localhost',
port: 5432,
username: 'admin',
password: 'secret',
databaseName: 'myapp',
},
cache: {
enabled: true,
ttl: 3600,
maxEntries: 1000,
},
logging: {
level: 'info' as const,
filePath: '/var/log/app.log',
maxSize: '10MB',
},
} as const
type ConfigType = typeof configObject
type DatabaseConfig = typeof configObject.database
type LogLevel = typeof configObject.logging.level // "info"
从函数提取类型信息:
typeof 可以提取函数的完整类型信息,包括参数和返回值:
// 函数类型提取
function calculateTotal(itemList: { name: string; price: number; quantity: number }[], discountRate: number = 0): { total: number; discountAmount: number; finalPrice: number } {
const total = itemList.reduce((sum, item) => sum + item.price * item.quantity, 0)
const discountAmount = total * discountRate
return {
total,
discountAmount,
finalPrice: total - discountAmount,
}
}
type CalculateFunctionType = typeof calculateTotal
type CalculateParameterTypes = Parameters<typeof calculateTotal>
type CalculateReturnType = ReturnType<typeof calculateTotal>
从类提取类型信息:
typeof 在处理类时有特殊的行为,可以提取构造函数类型和实例类型:
// 类类型提取
class UserManager {
private userList: Map<string, any> = new Map()
addUser(user: { id: string; name: string }): void {
this.userList.set(user.id, user)
}
getUser(id: string): any {
return this.userList.get(id)
}
static createInstance(): UserManager {
return new UserManager()
}
}
type UserManagerType = typeof UserManager // 构造函数类型
type UserManagerInstanceType = InstanceType<typeof UserManager>
typeof 与对象类型提取
typeof 最常见的用途是从复杂对象中提取类型定义,避免重复定义类型。
最佳实践:
- 使用 as const 获得精确的字面量类型
- 结合 keyof 进行属性枚举
- 通过索引访问获取嵌套类型
- 构建类型安全的配置系统
// 主题配置系统
const themeConfig = {
colors: {
primary: '#3498db',
secondary: '#2ecc71',
warning: '#f39c12',
danger: '#e74c3c',
text: {
primary: '#2c3e50',
secondary: '#7f8c8d',
disabled: '#bdc3c7',
},
},
fonts: {
sizes: {
small: '12px',
medium: '14px',
large: '16px',
xlarge: '20px',
},
weights: {
light: 300,
normal: 400,
bold: 600,
extraBold: 800,
},
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
},
breakpoints: {
mobile: '768px',
tablet: '1024px',
desktop: '1200px',
},
} as const
// 类型提取
type ThemeType = typeof themeConfig
type ColorConfig = typeof themeConfig.colors
type FontSizes = typeof themeConfig.fonts.sizes
type SpacingValues = (typeof themeConfig.spacing)[keyof typeof themeConfig.spacing]
// 主题工具函数
function getThemeValue<Path extends keyof ThemeType>(path: Path): ThemeType[Path] {
return themeConfig[path]
}
function getNestedValue<Category extends keyof ThemeType, Key extends keyof ThemeType[Category]>(category: Category, key: Key): ThemeType[Category][Key] {
return themeConfig[category][key]
}
// 使用示例
const primaryColor = getNestedValue('colors', 'primary') // "#3498db"
const mediumFonts = getNestedValue('fonts', 'sizes') // { small: "12px", ... }
// API路由配置
const apiRoutes = {
users: {
list: '/api/users',
detail: '/api/users/:id',
create: '/api/users',
update: '/api/users/:id',
delete: '/api/users/:id',
},
products: {
list: '/api/products',
detail: '/api/products/:id',
search: '/api/products/search',
},
orders: {
list: '/api/orders',
create: '/api/orders',
updateStatus: '/api/orders/:id/status',
},
} as const
type ApiRoutesType = typeof apiRoutes
type UserRoutes = typeof apiRoutes.users
type AllRouteValues = ApiRoutesType[keyof ApiRoutesType][keyof ApiRoutesType[keyof ApiRoutesType]]
// 路由构建器
function buildURL<Module extends keyof ApiRoutesType, Endpoint extends keyof ApiRoutesType[Module]>(module: Module, endpoint: Endpoint, params?: Record<string, string | number>): string {
let url = apiRoutes[module][endpoint] as string
if (params) {
Object.entries(params).forEach(([key, value]) => {
url = url.replace(`:${key}`, String(value))
})
}
return url
}
const userDetailURL = buildURL('users', 'detail', { id: '123' }) // "/api/users/123"
typeof 与枚举类型
typeof 在处理枚举类型时有特殊的行为和用途。
关键概念:
- 枚举的值类型与键类型的区别
- 数字枚举与字符串枚举的 typeof 行为
- 枚举的反向映射特性
- const 断言的影响
// 字符串枚举
enum UserRole {
Admin = 'ADMIN',
Editor = 'EDITOR',
Viewer = 'VIEWER',
Guest = 'GUEST',
}
type RoleKeys = keyof typeof UserRole // "Admin" | "Editor" | "Viewer" | "Guest"
type RoleValues = (typeof UserRole)[keyof typeof UserRole] // "ADMIN" | "EDITOR" | "VIEWER" | "GUEST"
// 数字枚举
enum OrderStatus {
Pending, // 0
Paid, // 1
Shipped, // 2
Completed, // 3
Cancelled, // 4
}
type StatusKeys = keyof typeof OrderStatus // "Pending" | "Paid" | "Shipped" | "Completed" | "Cancelled"
type StatusValues = (typeof OrderStatus)[keyof typeof OrderStatus] // 0 | 1 | 2 | 3 | 4
// 枚举工具函数
function isValidRole(value: string): value is UserRole {
return Object.values(UserRole).includes(value as UserRole)
}
function getRoleDisplayName(role: UserRole): string {
const displayNameMap: Record<UserRole, string> = {
[UserRole.Admin]: 'System Administrator',
[UserRole.Editor]: 'Content Editor',
[UserRole.Viewer]: 'Read-only User',
[UserRole.Guest]: 'Guest User',
}
return displayNameMap[role]
}
// 对象枚举替代方案
const permissionLevels = {
read: 'READ',
write: 'WRITE',
delete: 'DELETE',
admin: 'ADMIN',
} as const
type PermissionKeys = keyof typeof permissionLevels
type PermissionValues = (typeof permissionLevels)[keyof typeof permissionLevels]
// 权限检查函数
function checkPermission<T extends PermissionValues>(userPermissions: T[], requiredPermission: T): boolean {
return userPermissions.includes(requiredPermission)
}
const userPermissionList: PermissionValues[] = ['READ', 'WRITE']
const canDelete = checkPermission(userPermissionList, permissionLevels.delete) // false
第 4 章:索引访问类型的精确控制
索引访问类型基础
索引访问类型允许我们查询另一个类型上的特定属性类型,提供了精确的类型提取能力。
核心概念:
- 使用方括号语法访问类型的属性
- 支持联合类型作为索引
- 可以与其他类型运算符组合
- 在数组和元组上的特殊应用
设计原理:
- 类型级别的属性访问模拟
- 联合类型的分发特性
- 嵌套类型的递归访问
- 条件类型的集成应用
// 基础索引访问
interface ECommerceSystem {
user: {
profile: {
username: string
email: string
avatar: string
}
preferences: {
language: 'zh' | 'en'
theme: 'light' | 'dark'
notifications: boolean
}
cart: {
items: Array<{
productId: string
quantity: number
price: number
}>
totalAmount: number
}
}
product: {
basicInfo: {
productId: string
name: string
description: string
category: string
}
pricing: {
originalPrice: number
currentPrice: number
discount: number
}
inventory: {
totalStock: number
availableStock: number
reservedStock: number
}
}
}
// 精确类型提取
type UserInfo = ECommerceSystem['user']
type UserProfile = ECommerceSystem['user']['profile']
type UsernameType = ECommerceSystem['user']['profile']['username'] // string
type LanguageOptions = ECommerceSystem['user']['preferences']['language'] // "zh" | "en"
// 联合索引访问
type UserRelatedTypes = ECommerceSystem['user']['profile' | 'preferences']
type ProductPricingRelated = ECommerceSystem['product']['pricing' | 'inventory']
// 动态索引访问
type GetNestedType<ObjectType, Path1 extends keyof ObjectType, Path2 extends keyof ObjectType[Path1]> = ObjectType[Path1][Path2]
type CartType = GetNestedType<ECommerceSystem, 'user', 'cart'>
// 数组元素类型提取
type CartItem = ECommerceSystem['user']['cart']['items'][number]
type ProductIdType = CartItem['productId'] // string
数组与元组的索引访问
索引访问类型在处理数组和元组时展现出强大的类型提取能力。
核心技术:
- number 索引获取数组元素类型
- 具体数字索引获取元组特定位置类型
- length 属性获取元组长度
- 数组方法的返回类型提取
// 数组类型操作
const userList = [
{ ID: 1, name: 'John', department: 'Engineering', salary: 8000 },
{ ID: 2, name: 'Jane', department: 'Sales', salary: 6000 },
{ ID: 3, name: 'Bob', department: 'HR', salary: 7000 },
]
type UserArrayType = typeof userList
type SingleUserType = UserArrayType[number] // 数组元素类型
type UserIdType = SingleUserType['ID'] // number
type UserNameType = SingleUserType['name'] // string
// 元组类型操作
type CoordinateTuple = [x: number, y: number, z?: number]
type XCoordinateType = CoordinateTuple[0] // number
type YCoordinateType = CoordinateTuple[1] // number
type ZCoordinateType = CoordinateTuple[2] // number | undefined
type CoordinateLength = CoordinateTuple['length'] // 2 | 3
// 复杂数组类型提取
interface OrderData {
orderList: Array<{
orderId: string
customerInfo: {
customerId: string
customerName: string
contactInfo: string
}
items: Array<{
productCode: string
productName: string
unitPrice: number
quantity: number
subtotal: number
}>
status: 'pending' | 'paid' | 'shipped' | 'completed' | 'cancelled'
createdAt: Date
}>
}
type OrderType = OrderData['orderList'][number]
type CustomerInfoType = OrderType['customerInfo']
type OrderItemType = OrderType['items'][number]
type OrderStatusType = OrderType['status']
// 数组操作工具类型
type ArrayElement<T> = T extends readonly (infer U)[] ? U : never
type ArrayFirst<T extends readonly unknown[]> = T extends readonly [infer F, ...unknown[]] ? F : never
type ArrayLast<T extends readonly unknown[]> = T extends readonly [...unknown[], infer L] ? L : never
type ArrayTail<T extends readonly unknown[]> = T extends readonly [unknown, ...infer R] ? R : never
// 使用示例
type StatusList = ['pending', 'processing', 'completed']
type FirstStatus = ArrayFirst<StatusList> // "pending"
type LastStatus = ArrayLast<StatusList> // "completed"
type RestStatuses = ArrayTail<StatusList> // ["processing", "completed"]
// 深度索引访问
type DeepGet<T, K extends string> = K extends `${infer K1}.${infer K2}` ? (K1 extends keyof T ? DeepGet<T[K1], K2> : never) : K extends keyof T ? T[K] : never
type DeepTest = DeepGet<ECommerceSystem, 'user.profile.username'> // string
条件索引访问
结合条件类型,索引访问可以实现更加智能的类型提取和转换。
高级技术:
- 条件类型中的索引访问
- 分布式条件类型的索引应用
- 递归索引访问
- 类型守卫与索引访问的结合
// 条件索引访问
type GetFunctionProperties<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T]
type GetNonFunctionProperties<T> = {
[K in keyof T]: T[K] extends Function ? never : K
}[keyof T]
// 示例类型
interface MixedObject {
name: string
age: number
getInfo(): string
setAge(age: number): void
isAdult: boolean
calculate(): number
}
type FunctionPropertyNames = GetFunctionProperties<MixedObject> // "getInfo" | "setAge" | "calculate"
type DataPropertyNames = GetNonFunctionProperties<MixedObject> // "name" | "age" | "isAdult"
// 按类型过滤属性
type FilterByType<T, U> = {
[K in keyof T]: T[K] extends U ? K : never
}[keyof T]
type StringProperties = FilterByType<MixedObject, string> // "name"
type NumberProperties = FilterByType<MixedObject, number> // "age"
type BooleanProperties = FilterByType<MixedObject, boolean> // "isAdult"
// 递归属性访问
type FlattenObject<T, Prefix extends string = ''> = {
[K in keyof T as T[K] extends object ? (T[K] extends Function ? `${Prefix}${string & K}` : never) : `${Prefix}${string & K}`]: T[K] extends object
? T[K] extends Function
? T[K]
: FlattenObject<T[K], `${Prefix}${string & K}.`>
: T[K]
}[keyof T]
// 智能属性选择器
type SmartSelect<T, K extends string> = K extends keyof T ? T[K] : K extends `${infer K1}.${infer K2}` ? (K1 extends keyof T ? SmartSelect<T[K1], K2> : never) : never
// 使用示例
interface ComplexStructure {
basic: {
id: number
name: string
}
details: {
description: string
tags: string[]
metadata: {
creator: string
createdAt: Date
}
}
}
type IdType = SmartSelect<ComplexStructure, 'basic.id'> // number
type CreatorType = SmartSelect<ComplexStructure, 'details.metadata.creator'> // string
// 类型安全的属性访问函数
function safeAccess<T, K extends string>(object: T, path: K): SmartSelect<T, K> {
const pathArray = path.split('.')
let currentValue: any = object
for (const key of pathArray) {
if (currentValue && typeof currentValue === 'object' && key in currentValue) {
currentValue = currentValue[key]
} else {
return undefined as any
}
}
return currentValue
}
const complexObject: ComplexStructure = {
basic: { id: 1, name: 'Test' },
details: {
description: 'Description',
tags: ['tag1'],
metadata: { creator: 'admin', createdAt: new Date() },
},
}
const idValue = safeAccess(complexObject, 'basic.id') // number类型
const creator = safeAccess(complexObject, 'details.metadata.creator') // string类型
第 5 章:条件类型的逻辑推理
条件类型基础语法
条件类型是 TypeScript 类型系统中的逻辑分支,类似于 JavaScript 中的三元运算符,但作用于类型层面。
核心语法:
T extends U ? X : Y
理解要点:
- extends 关键字表示"可分配给"的关系
- 条件类型支持嵌套使用
- 分布式条件类型的特殊行为
- infer 关键字的类型推断能力
基础条件类型示例:
让我们从最简单的条件类型开始,理解它的基本语法:
// 基础条件类型
type IsString<T> = T extends string ? true : false
type Test1 = IsString<string> // true
type Test2 = IsString<number> // false
检测函数类型:
条件类型可以用来检测某个类型是否为函数:
// 函数类型检测
type IsFunction<T> = T extends (...args: any[]) => any ? true : false
type FunctionTest1 = IsFunction<() => void> // true
type FunctionTest2 = IsFunction<string> // false
检测数组类型:
同样的,我们可以检测某个类型是否为数组:
// 数组类型检测
type IsArray<T> = T extends any[] ? true : false
type ArrayTest1 = IsArray<string[]> // true
type ArrayTest2 = IsArray<string> // false
infer 关键字的类型推断
infer 关键字允许我们在条件类型中推断并捕获类型信息。
提取数组元素类型:
infer 最常见的用法是提取数组的元素类型:
// 提取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : never
type StringElement = ElementType<string[]> // string
提取函数返回类型:
infer 可以推断并提取函数的返回类型:
// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type FunctionReturn = ReturnType<() => string> // string
提取函数参数类型:
同样,infer 也能提取函数的参数类型:
// 提取函数参数类型
type ParameterTypes<T> = T extends (...args: infer P) => any ? P : never
type FunctionParams = ParameterTypes<(a: string, b: number) => void> // [string, number]
提取 Promise 内部类型:
infer 在处理异步类型时也很有用:
// 提取Promise内部类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
type PromiseContent = UnwrapPromise<Promise<string>> // string
第 6 章:映射类型的转换艺术
映射类型基础
映射类型允许基于现有类型创建新类型,通过遍历类型的键来构建新的类型结构。
基础映射语法:
映射类型的基本语法是遍历类型的每个属性:
// 基础映射语法
type MappingExample<T> = {
[K in keyof T]: T[K]
}
可选化所有属性:
我们可以将对象的所有属性变为可选:
// 可选化所有属性
type Optional<T> = {
[K in keyof T]?: T[K]
}
只读化所有属性:
同样,可以将所有属性变为只读:
// 只读化所有属性
type Readonly<T> = {
readonly [K in keyof T]: T[K]
}
类型转换:
映射类型还可以转换属性的类型:
// 类型转换
type Stringify<T> = {
[K in keyof T]: string
}
高级映射技术
条件映射:
映射类型可以结合条件类型,根据属性类型进行不同的处理:
// 条件映射
type NonFunctionProperties<T> = {
[K in keyof T]: T[K] extends Function ? never : T[K]
}
键名重映射:
TypeScript 4.1+ 支持键名重映射,可以改变属性的名称:
// 键名重映射
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
过滤映射:
结合条件类型,我们可以过滤出特定类型的属性:
// 过滤映射
type FilterKeys<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
}
第 7 章:模板字面量类型
模板字面量基础
模板字面量类型允许通过模板字符串语法构建字符串类型。
基础模板字面量:
模板字面量类型使用反引号语法,可以动态构建字符串类型:
// 基础模板字面量
type Greeting<Name extends string> = `Hello ${Name}`
type GreetingJohn = Greeting<'John'> // "Hello John"
组合模板字面量:
可以组合多个类型参数来构建复杂的字符串类型:
// 组合模板字面量
type EventName<Action extends string, Target extends string> = `${Action}${Capitalize<Target>}`
type ClickButton = EventName<'click', 'button'> // "clickButton"
URL 构建:
模板字面量类型在构建 URL 路径时特别有用:
// URL构建
type ApiPath<Version extends string, Resource extends string> = `/api/${Version}/${Resource}`
type UserApi = ApiPath<'v1', 'users'> // "/api/v1/users"
模板字面量的高级应用
路径参数解析:
模板字面量类型可以解析 URL 路径中的参数:
// 路径解析
type ExtractPathParams<Path extends string> = Path extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractPathParams<Rest>
: Path extends `${string}:${infer Param}`
? Param
: never
type PathParams = ExtractPathParams<'/users/:id/posts/:postId'> // "id" | "postId"
CSS 属性生成:
在构建 CSS 相关的类型时,模板字面量类型也很有用:
// CSS属性生成
type CSSProperties<Prefix extends string> = `${Prefix}-color` | `${Prefix}-size` | `${Prefix}-style`
type BorderProperties = CSSProperties<'border'> // "border-color" | "border-size" | "border-style"
第 8 章:实战项目:构建类型安全的状态管理器
项目需求分析
构建一个类型安全的状态管理器,支持:
- 强类型的状态定义
- 类型安全的状态更新
- 自动的动作类型推导
- 中间件支持
// 状态管理器核心类型
interface StateManager<StateType> {
getState(): StateType
updateState<KeyName extends keyof StateType>(key: KeyName, value: StateType[KeyName]): void
subscribe(listener: (state: StateType) => void): () => void
}
// 动作类型定义
type Action<Type extends string, Payload = void> = Payload extends void ? { type: Type } : { type: Type; payload: Payload }
// 动作创建器
type ActionCreator<ActionType> = ActionType extends Action<infer T, infer P> ? (P extends void ? () => ActionType : (payload: P) => ActionType) : never
// 实现示例
interface UserState {
userInfo: { name: string; email: string } | null
loading: boolean
errorMessage: string | null
}
type UserAction = Action<'setUser', { name: string; email: string }> | Action<'setLoading', boolean> | Action<'setError', string> | Action<'clearUser'>
// 状态管理器实现
class SimpleStateManager<StateType> implements StateManager<StateType> {
private state: StateType
private listeners: ((state: StateType) => void)[] = []
constructor(initialState: StateType) {
this.state = initialState
}
getState(): StateType {
return this.state
}
updateState<KeyName extends keyof StateType>(key: KeyName, value: StateType[KeyName]): void {
this.state = { ...this.state, [key]: value }
this.notifyListeners()
}
subscribe(listener: (state: StateType) => void): () => void {
this.listeners.push(listener)
return () => {
const index = this.listeners.indexOf(listener)
if (index > -1) {
this.listeners.splice(index, 1)
}
}
}
private notifyListeners(): void {
this.listeners.forEach(listener => listener(this.state))
}
}
🎯 学习总结与下一步
核心知识回顾
通过第二天的学习,我们深入掌握了:
类型创建技术:
- 泛型编程:类型参数化和约束
- keyof 运算符:键名提取和联合类型
- typeof 运算符:类型查询和提取
- 索引访问类型:精确的类型访问
- 条件类型:类型级别的逻辑判断
- 映射类型:类型转换和变形
- 模板字面量类型:字符串类型构建
实际应用能力:
- 构建类型安全的工具函数
- 设计可复用的类型工具
- 实现复杂的类型推导
- 创建类型安全的 API