掌握了接口基础后,这篇文章将深入探讨接口的高级特性:函数类型接口、可索引类型、混合类型等,这些特性让接口能够描述更复杂的数据结构。
函数类型接口#
调用签名#
接口可以描述函数的类型:
// TypeScript 5.x
// 函数类型接口interface SearchFunc { (source: string, keyword: string): boolean}
// 实现函数const search: SearchFunc = (source, keyword) => { return source.includes(keyword)}
// 参数名不需要匹配const search2: SearchFunc = (src, kw) => { return src.indexOf(kw) !== -1}
console.log(search('hello world', 'world')) // trueconsole.log(search2('hello world', 'foo')) // false带属性的函数接口#
// 既是函数又有属性interface Counter { (start: number): number // 调用签名 interval: number // 属性 reset(): void // 方法}
function createCounter(): Counter { let count = 0
const counter = ((start: number) => { count = start return count }) as Counter
counter.interval = 1000 counter.reset = () => { count = 0 }
return counter}
const counter = createCounter()console.log(counter(10)) // 10console.log(counter.interval) // 1000counter.reset()构造函数接口#
// 描述构造函数interface ClockConstructor { new (hour: number, minute: number): ClockInterface}
interface ClockInterface { tick(): void}
// 实现class DigitalClock implements ClockInterface { constructor(hour: number, minute: number) { // 初始化 }
tick() { console.log('beep beep') }}
// 工厂函数使用构造函数接口function createClock( ctor: ClockConstructor, hour: number, minute: number): ClockInterface { return new ctor(hour, minute)}
const digital = createClock(DigitalClock, 12, 30)digital.tick()可索引类型#
数字索引#
// 数字索引(类似数组)interface StringArray { [index: number]: string}
const arr: StringArray = ['a', 'b', 'c']console.log(arr[0]) // 'a'
// 只读数字索引interface ReadonlyStringArray { readonly [index: number]: string}
const readonlyArr: ReadonlyStringArray = ['x', 'y', 'z']// readonlyArr[0] = 'a'; // ❌ 错误:只读字符串索引#
// 字符串索引(类似字典)interface StringDictionary { [key: string]: string}
const dict: StringDictionary = { name: '张三', email: 'test@test.com',}
dict.phone = '123456' // ✅ 可以动态添加
// 字符串索引会约束所有属性interface NumberDictionary { [key: string]: number length: number // ✅ number 兼容 // name: string; // ❌ 错误:string 不兼容 number}混合索引#
// 同时使用数字和字符串索引// 数字索引的返回值必须是字符串索引返回值的子类型interface Mixed { [index: number]: string // 数字索引返回 string [key: string]: string | number // 字符串索引返回 string | number length: number // ✅ 兼容}
// 为什么?因为 obj[0] 会被转换为 obj["0"]// 所以数字索引必须兼容字符串索引实际应用#
// CSS 样式对象interface CSSStyles { [property: string]: string | number width?: number | string height?: number | string}
const styles: CSSStyles = { width: 100, height: '50px', backgroundColor: '#fff', fontSize: 14,}
// 配置映射interface EnvConfig { [key: string]: string | undefined NODE_ENV: string API_URL: string DEBUG?: string}
const env: EnvConfig = { NODE_ENV: 'production', API_URL: 'https://api.example.com', CUSTOM_VAR: 'value',}混合类型#
一个接口可以同时描述函数、对象和更多类型:
// 混合类型:既是函数又是对象interface Lib { (): void // 可以作为函数调用 version: string // 有 version 属性 doSomething(): void // 有方法}
function createLib(): Lib { const lib = (() => { console.log('Lib called') }) as Lib
lib.version = '1.0.0' lib.doSomething = () => { console.log('Doing something...') }
return lib}
const myLib = createLib()myLib() // "Lib called"console.log(myLib.version) // "1.0.0"myLib.doSomething() // "Doing something..."链式调用接口#
interface QueryBuilder { select(fields: string[]): QueryBuilder from(table: string): QueryBuilder where(condition: string): QueryBuilder orderBy(field: string, direction?: 'asc' | 'desc'): QueryBuilder limit(count: number): QueryBuilder execute(): Promise<any[]>}
const query: QueryBuilder = { select(fields) { console.log('SELECT', fields.join(', ')) return this }, from(table) { console.log('FROM', table) return this }, where(condition) { console.log('WHERE', condition) return this }, orderBy(field, direction = 'asc') { console.log('ORDER BY', field, direction) return this }, limit(count) { console.log('LIMIT', count) return this }, async execute() { return [] },}
// 链式调用query .select(['id', 'name']) .from('users') .where('age > 18') .orderBy('name') .limit(10) .execute()接口的高级合并#
命名空间与接口合并#
// 接口可以和命名空间合并interface Document { createElement(tagName: string): Element}
namespace Document { export function isDocument(obj: any): obj is Document { return obj && typeof obj.createElement === 'function' }}
// 使用const doc: Document = { createElement(tagName: string) { return {} as Element },}
Document.isDocument(doc) // true全局接口扩展#
// 扩展全局接口declare global { interface Window { myCustomProperty: string }
interface Array<T> { customMethod(): T[] }}
// 使用(需要实现)window.myCustomProperty = 'hello'
Array.prototype.customMethod = function () { return [...this]}
export {} // 使文件成为模块模块接口扩展#
// 扩展第三方库的接口import { AxiosRequestConfig } from 'axios'
declare module 'axios' { interface AxiosRequestConfig { retry?: number retryDelay?: number }}
// 现在可以使用新属性const config: AxiosRequestConfig = { url: '/api/data', retry: 3, retryDelay: 1000,}接口与泛型#
// 泛型接口interface Container<T> { value: T getValue(): T setValue(value: T): void}
class Box<T> implements Container<T> { constructor(public value: T) {}
getValue(): T { return this.value }
setValue(value: T): void { this.value = value }}
const stringBox = new Box('hello')const numberBox = new Box(42)
// 泛型接口作为函数类型interface GenericFn<T> { (arg: T): T}
const identity: GenericFn<number> = (x) => x泛型约束#
// 接口作为泛型约束interface Lengthwise { length: number}
function logLength<T extends Lengthwise>(arg: T): T { console.log(arg.length) return arg}
logLength('hello') // 5logLength([1, 2, 3]) // 3logLength({ length: 10, value: 'test' }) // 10// logLength(123); // ❌ 错误:number 没有 length 属性接口的工具类型应用#
interface User { id: number name: string email: string password: string createdAt: Date}
// Partial:所有属性可选type PartialUser = Partial<User>
// Required:所有属性必需type RequiredUser = Required<PartialUser>
// Readonly:所有属性只读type ReadonlyUser = Readonly<User>
// Pick:选取部分属性type UserBasic = Pick<User, 'id' | 'name'>
// Omit:排除部分属性type UserWithoutPassword = Omit<User, 'password'>
// Record:创建对象类型type UserRoles = Record<string, User>
// 组合使用type UserUpdate = Partial<Omit<User, 'id' | 'createdAt'>>// { name?: string; email?: string; password?: string }实际应用场景#
事件处理器接口#
interface EventMap { click: MouseEvent keydown: KeyboardEvent scroll: Event custom: CustomEvent<{ data: string }>}
interface EventEmitter<T extends Record<string, Event>> { on<K extends keyof T>(event: K, handler: (e: T[K]) => void): void off<K extends keyof T>(event: K, handler: (e: T[K]) => void): void emit<K extends keyof T>(event: K, e: T[K]): void}
class MyEmitter implements EventEmitter<EventMap> { private handlers = new Map<string, Function[]>()
on<K extends keyof EventMap>(event: K, handler: (e: EventMap[K]) => void) { const list = this.handlers.get(event as string) || [] list.push(handler) this.handlers.set(event as string, list) }
off<K extends keyof EventMap>(event: K, handler: (e: EventMap[K]) => void) { const list = this.handlers.get(event as string) || [] const index = list.indexOf(handler) if (index > -1) list.splice(index, 1) }
emit<K extends keyof EventMap>(event: K, e: EventMap[K]) { const list = this.handlers.get(event as string) || [] list.forEach((handler) => handler(e)) }}状态管理接口#
interface State { user: User | null posts: Post[] loading: boolean}
interface Actions { login(credentials: { email: string; password: string }): Promise<void> logout(): void fetchPosts(): Promise<void>}
interface Store<S, A> { state: S actions: A subscribe(listener: (state: S) => void): () => void}
type AppStore = Store<State, Actions>常见问题#
🙋 接口可以描述类的静态部分吗?#
不能直接描述,需要分开定义:
// 实例接口interface ClockInterface { currentTime: Date setTime(d: Date): void}
// 构造函数接口(静态部分)interface ClockConstructor { new (hour: number, minute: number): ClockInterface staticMethod(): void}🙋 如何让索引签名支持多种类型?#
使用联合类型:
interface FlexibleObject { [key: string]: string | number | boolean | undefined requiredField: string // 必须兼容索引签名的类型}🙋 接口和抽象类有什么区别?#
// 接口:纯类型约束,无实现interface Flyable { fly(): void}
// 抽象类:可以有部分实现abstract class Animal { abstract makeSound(): void
move() { console.log('Moving...') }}总结#
| 特性 | 语法 | 用途 |
|---|---|---|
| 函数类型 | (arg: T): R | 描述函数签名 |
| 构造签名 | new (arg: T): R | 描述构造函数 |
| 数字索引 | [index: number]: T | 类数组结构 |
| 字符串索引 | [key: string]: T | 字典结构 |
| 混合类型 | 组合多种签名 | 复杂对象 |
| 接口合并 | 同名 interface | 扩展接口 |
下一篇我们将学习接口的继承与实现,了解如何通过继承复用接口定义。