掌握基础声明文件后,本��介绍更复杂的声明场景和高级技巧。
三斜线指令#
三斜线指令是声明文件之间的引用方式:
// TypeScript 5.x
// 引用其他声明文件/// <reference path="./other.d.ts" />
// 引用 @types 包/// <reference types="node" />
// 引用 ES 内置库/// <reference lib="es2015" />/// <reference lib="dom" />reference path#
引用本地声明文件���
// === types/base.d.ts ===interface BaseEntity { id: string createdAt: Date updatedAt: Date}
// === types/user.d.ts ===/// <reference path="./base.d.ts" />
interface User extends BaseEntity { name: string email: string}
// === types/index.d.ts ===/// <reference path="./base.d.ts" />/// <reference path="./user.d.ts" />reference types#
引用 npm 包中的类型:
// === my-library.d.ts ===/// <reference types="node" />
import { EventEmitter } from 'events'
export class MyEmitter extends EventEmitter { emit(event: 'data', data: Buffer): boolean emit(event: 'end'): boolean emit(event: string, ...args: unknown[]): boolean}reference lib#
引用内置库:
// 只引用需要的库/// <reference lib="es2015.promise" />/// <reference lib="es2015.iterable" />/// <reference lib="dom" />
// 而不是在 tsconfig.json 中配置复杂模块声明#
UMD 模块#
同时支持多种模块系统:
export as namespace MyLibrary
export function init(options: Options): voidexport function destroy(): void
export interface Options { debug?: boolean}
export default class MyLibrary { constructor(options?: Options) start(): void stop(): void}
// 使用方式
// ES 模块import MyLibrary from 'my-library'
// CommonJSconst MyLibrary = require('my-library')
// 全局变量(浏览器)MyLibrary.init({ debug: true })导出函数和命名空间#
export = _
declare function _(value: string): _.LoDashWrapper<string>declare function _(value: number): _.LoDashWrapper<number>declare function _<T>(value: T[]): _.LoDashArrayWrapper<T>declare function _<T extends object>(value: T): _.LoDashObjectWrapper<T>
declare namespace _ { interface LoDashWrapper<T> { value(): T }
interface LoDashArrayWrapper<T> { first(): T | undefined last(): T | undefined value(): T[] }
interface LoDashObjectWrapper<T> { keys(): string[] values(): unknown[] value(): T }
function map<T, R>(array: T[], fn: (item: T) => R): R[] function filter<T>(array: T[], fn: (item: T) => boolean): T[] function reduce<T, R>(array: T[], fn: (acc: R, item: T) => R, initial: R): R}
// 使用import _ from 'lodash'
const result = _([1, 2, 3]).first()const mapped = _.map([1, 2, 3], (n) => n * 2)可调用对象#
// 既是函数又有属性interface JQuery { (selector: string): JQueryElement (element: HTMLElement): JQueryElement (callback: () => void): void
ajax(options: AjaxOptions): Promise<unknown> get(url: string): Promise<unknown> post(url: string, data: unknown): Promise<unknown>
fn: JQueryPrototype version: string}
interface JQueryElement { html(): string html(content: string): this css(property: string): string css(property: string, value: string): this addClass(className: string): this removeClass(className: string): this on(event: string, handler: (e: Event) => void): this}
interface AjaxOptions { url: string method?: string data?: unknown}
interface JQueryPrototype { extend(plugin: Record<string, Function>): void}
declare const $: JQuery类的静态和实例#
// 分离静态和实例类型interface UserConstructor { new (name: string, email: string): UserInstance fromJSON(json: string): UserInstance defaultRole: string}
interface UserInstance { name: string email: string greet(): string toJSON(): string}
declare const User: UserConstructor
// 使用const user = new User('张三', 'test@test.com')user.greet()
const user2 = User.fromJSON('{}')console.log(User.defaultRole)全局声明#
在模块中声明全局#
export {}
declare global { // 全局变量 const __DEV__: boolean const __VERSION__: string
// 全局函数 function __log__(message: string): void
// 扩展 Window interface Window { dataLayer: unknown[] gtag(...args: unknown[]): void }
// 扩展 NodeJS namespace NodeJS { interface ProcessEnv { NODE_ENV: 'development' | 'production' | 'test' DATABASE_URL: string API_KEY: string } }
// 扩展内置类型 interface Array<T> { unique(): T[] groupBy<K extends string | number>(fn: (item: T) => K): Record<K, T[]> }
interface String { toTitleCase(): string }}环境声明#
/// <reference types="vite/client" />
interface ImportMetaEnv { readonly VITE_API_URL: string readonly VITE_APP_TITLE: string readonly VITE_DEBUG: string}
interface ImportMeta { readonly env: ImportMetaEnv}
// 使用console.log(import.meta.env.VITE_API_URL)声明合并#
接口合并#
// 扩展第三方库的接口import 'express-serve-static-core'
declare module 'express-serve-static-core' { interface Request { user?: { id: string role: string } startTime?: number }
interface Response { success<T>(data: T): void fail(error: Error): void }}
// 使用import express from 'express'
const app = express()
app.use((req, res, next) => { req.startTime = Date.now() next()})
app.get('/', (req, res) => { res.success({ message: 'ok' })})命名空间合并#
// 扩展枚举enum Color { Red, Green, Blue,}
namespace Color { export function fromString(str: string): Color | undefined { switch (str.toLowerCase()) { case 'red': return Color.Red case 'green': return Color.Green case 'blue': return Color.Blue default: return undefined } }
export function toString(color: Color): string { return Color[color].toLowerCase() }}
// 使用const color = Color.fromString('red')const str = Color.toString(Color.Blue)条件类型在声明中的应用#
// 根据参数类型返回不同类型declare function createElement<T extends keyof HTMLElementTagNameMap>( tag: T): HTMLElementTagNameMap[T]
declare function createElement(tag: string): HTMLElement
// 使用const div = createElement('div') // HTMLDivElementconst span = createElement('span') // HTMLSpanElementconst custom = createElement('my-element') // HTMLElement
// 事件处理器声明interface EventMap { click: MouseEvent keydown: KeyboardEvent focus: FocusEvent custom: CustomEvent}
declare function addEventListener<K extends keyof EventMap>( type: K, listener: (event: EventMap[K]) => void): void
declare function addEventListener( type: string, listener: (event: Event) => void): void
// 使用addEventListener('click', (e) => { console.log(e.clientX) // MouseEvent})
addEventListener('keydown', (e) => { console.log(e.key) // KeyboardEvent})泛型声明#
// 复杂泛型函数declare function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>
declare function omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>
declare function merge<T, U>(target: T, source: U): T & U
// Promise 工具declare function promiseAll<T extends readonly unknown[]>( promises: T): Promise<{ [K in keyof T]: Awaited<T[K]> }>
declare function promiseRace<T>(promises: Promise<T>[]): Promise<T>
// 类型推断declare function identity<T>(value: T): T
declare function pair<T, U>(first: T, second: U): [T, U]
// 条件泛型declare function isDefined<T>(value: T | undefined | null): value is T
declare function assertDefined<T>( value: T | undefined | null): asserts value is T实际案例#
React 组件声明#
import * as React from 'react'
declare module 'my-ui-library' { export interface ButtonProps { variant?: 'primary' | 'secondary' | 'danger' size?: 'small' | 'medium' | 'large' disabled?: boolean loading?: boolean onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void children: React.ReactNode }
export const Button: React.FC<ButtonProps>
export interface InputProps { value?: string defaultValue?: string placeholder?: string onChange?: (value: string) => void onBlur?: () => void }
export const Input: React.ForwardRefExoticComponent< InputProps & React.RefAttributes<HTMLInputElement> >
export interface ModalProps { open: boolean onClose: () => void title?: string children: React.ReactNode }
export const Modal: React.FC<ModalProps>}数据库客户端声明#
declare module 'my-database' { export interface QueryResult<T> { rows: T[] rowCount: number command: string }
export interface ConnectionConfig { host: string port: number database: string user: string password: string }
export class Client { constructor(config: ConnectionConfig) connect(): Promise<void> disconnect(): Promise<void> query<T = unknown>(sql: string, params?: unknown[]): Promise<QueryResult<T>> transaction<T>(fn: (client: Client) => Promise<T>): Promise<T> }
export class Pool { constructor(config: ConnectionConfig & { max?: number }) query<T = unknown>(sql: string, params?: unknown[]): Promise<QueryResult<T>> getClient(): Promise<Client> end(): Promise<void> }}API 客户端声明#
declare module 'api-client' { export interface RequestConfig { baseURL?: string timeout?: number headers?: Record<string, string> }
export interface Response<T> { data: T status: number headers: Record<string, string> }
export interface ApiClient { get<T>(url: string, config?: RequestConfig): Promise<Response<T>> post<T>( url: string, data?: unknown, config?: RequestConfig ): Promise<Response<T>> put<T>( url: string, data?: unknown, config?: RequestConfig ): Promise<Response<T>> delete<T>(url: string, config?: RequestConfig): Promise<Response<T>>
interceptors: { request: InterceptorManager<RequestConfig> response: InterceptorManager<Response<unknown>> } }
export interface InterceptorManager<T> { use( onFulfilled?: (value: T) => T | Promise<T>, onRejected?: (error: unknown) => unknown ): number eject(id: number): void }
export function create(config?: RequestConfig): ApiClient
const defaultClient: ApiClient export default defaultClient}常见问题#
🙋 如何处理 this 类型?#
// 链式调用声明declare class Builder<T> { set<K extends string, V>(key: K, value: V): Builder<T & { [P in K]: V }> build(): T}
// 使用const obj = new Builder().set('name', '张三').set('age', 25).build()// { name: string; age: number }🙋 如何声明回调函数?#
// 回调函数类型type Callback<T> = (error: Error | null, result?: T) => void
declare function readFile(path: string, callback: Callback<string>): void
declare function readFileAsync(path: string): Promise<string>
// 事件回调type EventCallback<T> = (event: T) => void
declare function on<K extends keyof EventMap>( event: K, callback: EventCallback<EventMap[K]>): void🙋 如何测试声明文件?#
// 创建测试文件 test.tsimport MyLib from 'my-lib'
// 测试类型是否正确const lib = new MyLib() // $ExpectType MyLiblib.method() // $ExpectType string
// 测试类型错误// @ts-expect-errorlib.method(123) // 应该报错
// 运行 tsc --noEmit 检查总结#
| 特性 | 语法 | 用途 |
|---|---|---|
| reference path | /// <reference path="" /> | 引用本地声明 |
| reference types | /// <reference types="" /> | 引用类型包 |
| reference lib | /// <reference lib="" /> | 引用内置库 |
| UMD | export as namespace | 支持全局和模块 |
| 全局声明 | declare global | 模块中添加全局 |
| 声明合并 | 同名声明 | 扩展现有类型 |
下一篇我们将学习 tsconfig 配置,了解 TypeScript 项目的配置选项。