Skip to content

接口进阶

掌握了接口基础后,这篇文章将深入探讨接口的高级特性:函数类型接口、可索引类型、混合类型等,这些特性让接口能够描述更复杂的数据结构。

函数类型接口#

调用签名#

接口可以描述函数的类型:

// 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')) // true
console.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)) // 10
console.log(counter.interval) // 1000
counter.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') // 5
logLength([1, 2, 3]) // 3
logLength({ 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扩展接口

下一篇我们将学习接口的继承与实现,了解如何通过继承复用接口定义。