0143. 模块的类型声明
- 1. 🎯 本节内容
- 2. 🫧 评价
- 3. 🤔 什么是模块的类型声明?
- 4. 🤔 为什么需要类型声明文件?
- 5. 🤔 类型声明文件的基本语法是什么?
- 6. 🤔 如何为第三方模块添加类型声明?
- 7. 🤔 模块类型声明的查找规则是什么?
- 8. 🤔 如何声明全局模块?
- 9. 🤔 如何声明命名空间模块?
- 10. 🤔 如何扩展现有模块的类型?
- 11. 🤔 如何处理非 JS/TS 文件的导入?
- 12. 🤔 declare module 和 declare namespace 有什么区别?
- 13. 🤔 @types 包是什么?
- 14. 🤔 如何编写可发布的类型声明文件?
- 15. 🤔 最佳实践是什么?
- 16. 🔗 引用
1. 🎯 本节内容
- 类型声明文件(
.d.ts) declare module语法- 为第三方模块添加类型
- 模块扩展(Module Augmentation)
- 全局模块声明
- @types 包
2. 🫧 评价
- 类型声明文件是 TypeScript 项目中不可或缺的一部分,它为 JavaScript 代码提供类型信息。
- 理解模块的类型声明有助于:
- 为没有类型定义的第三方库添加类型支持
- 声明非代码文件(如图片、CSS)的类型
- 扩展现有模块的类型定义
- 发布自己的类型安全的库
- 在实际开发中,我们经常需要编写或修改类型声明文件,掌握这个知识点能够显著提升开发体验。
3. 🤔 什么是模块的类型声明?
模块的类型声明是使用 .d.ts 文件来描述模块的类型信息,而不包含实际的实现代码。这些文件只在 TypeScript 编译时使用,编译后会被删除。
类型声明文件的作用:
- 为 JavaScript 模块提供类型信息
- 声明全局变量、函数、类等的类型
- 扩展现有模块的类型定义
- 声明非代码资源(CSS、图片等)的类型
// JavaScript 实现文件
export class User {
constructor(name, age) {
this.name = name
this.age = age
}
greet() {
return `Hello, ${this.name}`
}
}2
3
4
5
6
7
8
9
10
11
// 类型声明文件
export declare class User {
constructor(name: string, age: number)
name: string
age: number
greet(): string
}2
3
4
5
6
7
// 使用时有完整的类型信息
import { User } from './user'
const user = new User('Alice', 25) // 类型检查正常
user.greet() // 返回 string2
3
4
5
4. 🤔 为什么需要类型声明文件?
为 JavaScript 库提供类型支持
很多第三方库是用 JavaScript 编写的,没有类型信息。
// 没有类型声明时
import _ from 'lodash'
_.map([1, 2, 3], (n) => n * 2) // TypeScript 不知道 _.map 的类型
// 安装 @types/lodash 后
import _ from 'lodash'
_.map([1, 2, 3], (n) => n * 2) // 有完整的类型提示和检查2
3
4
5
6
7
分离类型定义和实现
在发布 npm 包时,可以将类型定义和实现分离。
// 实现文件
export function add(a, b) {
return a + b
}2
3
4
// 类型声明文件
export declare function add(a: number, b: number): number2
声明全局类型
为全局变量、函数提供类型定义。
// global.d.ts
declare const APP_VERSION: string
declare function initApp(): void
// 使用时
console.log(APP_VERSION) // string 类型
initApp() // 无需导入,直接使用2
3
4
5
6
7
5. 🤔 类型声明文件的基本语法是什么?
声明变量
// types.d.ts
declare const version: string
declare let count: number
declare var config: { apiUrl: string }2
3
4
声明函数
// types.d.ts
declare function add(a: number, b: number): number
declare function greet(name: string): void
// 函数重载
declare function createElement(tag: 'div'): HTMLDivElement
declare function createElement(tag: 'span'): HTMLSpanElement
declare function createElement(tag: string): HTMLElement2
3
4
5
6
7
8
声明类
// types.d.ts
declare class User {
constructor(name: string, age: number)
name: string
age: number
greet(): string
}2
3
4
5
6
7
声明接口和类型别名
// types.d.ts
declare interface Config {
apiUrl: string
timeout: number
}
declare type Status = 'pending' | 'success' | 'error'2
3
4
5
6
7
声明模块
// types.d.ts
declare module 'my-module' {
export function doSomething(): void
export const version: string
}2
3
4
5
6. 🤔 如何为第三方模块添加类型声明?
为没有类型定义的 npm 包添加类型
假设使用的第三方库 awesome-lib 没有类型定义。
// types/awesome-lib.d.ts
declare module 'awesome-lib' {
export function doSomething(param: string): void
export class AwesomeClass {
constructor(config: { name: string })
getName(): string
}
}2
3
4
5
6
7
8
// types/awesome-lib.d.ts
// 允许导入但不提供类型检查
declare module 'awesome-lib'2
3
// app.ts
import { doSomething, AwesomeClass } from 'awesome-lib'
doSomething('test') // 有类型检查
const instance = new AwesomeClass({ name: 'test' })2
3
4
5
配置 TypeScript 识别类型声明
// tsconfig.json
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./types"],
"types": ["node", "jest"]
}
}2
3
4
5
6
7
7. 🤔 模块类型声明的查找规则是什么?
TypeScript 按以下顺序查找模块的类型声明:
- 查找同名的
.d.ts文件
export function add(a, b) {
return a + b
}2
3
export declare function add(a: number, b: number): number- 查找 package.json 中的 types 或 typings 字段
// package.json
{
"name": "my-lib",
"main": "dist/index.js",
"types": "dist/index.d.ts"
}2
3
4
5
6
- 查找 node_modules/@types 目录
node_modules/
@types/
lodash/
index.d.ts
react/
index.d.ts2
3
4
5
6
- 查找 typeRoots 配置的目录
// tsconfig.json
{
"compilerOptions": {
"typeRoots": ["./types", "./node_modules/@types"]
}
}2
3
4
5
6
8. 🤔 如何声明全局模块?
声明全局变量
// global.d.ts
declare global {
const APP_VERSION: string
const DEBUG: boolean
interface Window {
myCustomProperty: string
}
}
export {}2
3
4
5
6
7
8
9
10
11
使用全局声明:
// app.ts
console.log(APP_VERSION) // string
console.log(window.myCustomProperty) // string2
3
注意事项:
- 在包含
import或export的文件中,需要使用declare global块 - 如果文件中没有
import或export,整个文件会被视为全局作用域,可以直接使用declare
// types.d.ts
import { User } from './user'
declare global {
const currentUser: User
}
export {}2
3
4
5
6
7
8
// types.d.ts
declare const APP_VERSION: string
declare const DEBUG: boolean2
3
9. 🤔 如何声明命名空间模块?
使用 declare module 声明模块
// types.d.ts
declare module 'my-library' {
export interface Config {
apiUrl: string
}
export function init(config: Config): void
export class Client {
constructor(config: Config)
request(url: string): Promise<any>
}
}2
3
4
5
6
7
8
9
10
11
12
13
使用声明的模块:
// app.ts
import { init, Client, Config } from 'my-library'
const config: Config = { apiUrl: 'https://api.example.com' }
init(config)
const client = new Client(config)2
3
4
5
6
7
声明具有子路径的模块
// types.d.ts
declare module 'my-library' {
export function main(): void
}
declare module 'my-library/utils' {
export function helper(): void
}
declare module 'my-library/types' {
export interface User {
name: string
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
使用子路径模块:
// app.ts
import { main } from 'my-library'
import { helper } from 'my-library/utils'
import type { User } from 'my-library/types'2
3
4
10. 🤔 如何扩展现有模块的类型?
模块扩展(Module Augmentation)允许我们为现有模块添加新的类型定义。
扩展第三方库
// types/express.d.ts
import 'express'
declare module 'express' {
interface Request {
user?: {
id: string
name: string
}
}
}2
3
4
5
6
7
8
9
10
11
// app.ts
import express from 'express'
const app = express()
app.get('/', (req, res) => {
// req.user 现在有类型定义
console.log(req.user?.name)
})2
3
4
5
6
7
8
9
扩展全局接口
// types/global.d.ts
declare global {
interface Window {
myApp: {
version: string
init(): void
}
}
interface Array<T> {
myCustomMethod(): T[]
}
}
export {}2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用扩展:
// app.ts
window.myApp.version // string
window.myApp.init() // 方法调用
const arr = [1, 2, 3]
arr.myCustomMethod() // number[]2
3
4
5
6
扩展命名空间
// types.d.ts
import * as _ from 'lodash'
declare module 'lodash' {
interface LoDashStatic {
myCustomFunction(input: string): string
}
}2
3
4
5
6
7
8
11. 🤔 如何处理非 JS/TS 文件的导入?
声明 CSS 模块
declare module '*.css' {
const content: { [className: string]: string }
export default content
}
declare module '*.module.css' {
const classes: { [key: string]: string }
export default classes
}2
3
4
5
6
7
8
9
// app.ts
import styles from './app.module.css'
console.log(styles.container) // string2
3
4
声明图片资源
// types/images.d.ts
declare module '*.png' {
const content: string
export default content
}
declare module '*.jpg' {
const content: string
export default content
}
declare module '*.svg' {
import React from 'react'
const SVG: React.FC<React.SVGProps<SVGSVGElement>>
export default SVG
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
使用图片:
// app.ts
import logo from './logo.png'
import Icon from './icon.svg'
console.log(logo) // string (图片 URL)
<Icon width={24} height={24} /> // React 组件2
3
4
5
6
声明 JSON 文件
// types/json.d.ts
declare module '*.json' {
const value: any
export default value
}2
3
4
5
使用 JSON:
// app.ts
import config from './config.json'
console.log(config.apiUrl) // any 类型2
3
4
声明其他资源
// types/assets.d.ts
declare module '*.mp4' {
const src: string
export default src
}
declare module '*.woff' {
const src: string
export default src
}
declare module '*.woff2' {
const src: string
export default src
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
12. 🤔 declare module 和 declare namespace 有什么区别?
declare module 用于模块声明
用于声明 ES6 模块或外部模块。
// types.d.ts
declare module 'my-library' {
export function doSomething(): void
export class MyClass {}
}
// 使用
import { doSomething, MyClass } from 'my-library'2
3
4
5
6
7
8
declare namespace 用于命名空间声明
用于声明全局命名空间或内部模块(旧式模块系统)。
// types.d.ts
declare namespace MyNamespace {
function doSomething(): void
class MyClass {}
namespace Nested {
function helper(): void
}
}
// 使用(无需导入)
MyNamespace.doSomething()
const instance = new MyNamespace.MyClass()
MyNamespace.Nested.helper()2
3
4
5
6
7
8
9
10
11
12
13
14
区别对比:
| 特性 | declare module | declare namespace |
|---|---|---|
| 用途 | ES6 模块 | 全局命名空间 |
| 导入 | 需要 import | 不需要导入 |
| 作用域 | 模块作用域 | 全局作用域 |
| 推荐使用 | 是(现代开发) | 否(旧式,向后兼容) |
13. 🤔 @types 包是什么?
@types 是 DefinitelyTyped 项目的 npm 组织,为没有类型定义的 JavaScript 库提供类型声明。
安装 @types 包
# 为 lodash 安装类型定义
npm install --save-dev @types/lodash
# 为 node 安装类型定义
npm install --save-dev @types/node
# 为 react 安装类型定义
npm install --save-dev @types/react2
3
4
5
6
7
8
使用 @types 包:
// 安装 @types/lodash 后
import _ from 'lodash'
// 现在有完整的类型提示
_.map([1, 2, 3], (n) => n * 2) // number[]
_.chunk([1, 2, 3, 4], 2) // number[][]2
3
4
5
6
查找可用的 @types 包:
- 访问 DefinitelyTyped 仓库
- 访问 TypeSearch 网站搜索
- 在 npm 上搜索
@types/库名
自动安装类型
某些包管理器支持自动安装类型:
# pnpm 自动安装 @types
pnpm install lodash
# yarn 也可以配置自动安装2
3
4
14. 🤔 如何编写可发布的类型声明文件?
项目结构
my-library/
├── src/
│ ├── index.ts
│ ├── user.ts
│ └── utils.ts
├── dist/
│ ├── index.js
│ ├── index.d.ts
│ ├── user.js
│ ├── user.d.ts
│ └── ...
├── package.json
└── tsconfig.json2
3
4
5
6
7
8
9
10
11
12
13
配置 tsconfig.json
// tsconfig.json
{
"compilerOptions": {
"declaration": true, // 生成 .d.ts 文件
"declarationMap": true, // 生成 .d.ts.map 源映射
"outDir": "./dist", // 输出目录
"rootDir": "./src",
"module": "ESNext",
"target": "ES2020"
},
"include": ["src/**/*"]
}2
3
4
5
6
7
8
9
10
11
12
配置 package.json
// package.json
{
"name": "my-library",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./utils": {
"types": "./dist/utils.d.ts",
"import": "./dist/utils.js"
}
},
"files": ["dist"]
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
编写源代码
export { User } from './user'
export * from './utils'
export interface Config {
apiUrl: string
timeout?: number
}
export function init(config: Config): void {
console.log('初始化', config)
}2
3
4
5
6
7
8
9
10
11
export class User {
constructor(public name: string, public age: number) {}
greet(): string {
return `Hello, ${this.name}`
}
}2
3
4
5
6
7
export function add(a: number, b: number): number {
return a + b
}
export function subtract(a: number, b: number): number {
return a - b
}2
3
4
5
6
7
编译和发布
# 编译生成 .js 和 .d.ts 文件
npm run build # 执行 tsc
# 发布到 npm
npm publish2
3
4
5
使用发布的库:
// 其他项目中使用
import { User, init, add } from 'my-library'
import { subtract } from 'my-library/utils'
// 有完整的类型支持
const user = new User('Alice', 25)
init({ apiUrl: 'https://api.example.com' })2
3
4
5
6
7
15. 🤔 最佳实践是什么?
使用 declare 关键字
在 .d.ts 文件中始终使用 declare 关键字。
// ✅ 推荐
declare function add(a: number, b: number): number
declare class User {
name: string
}
// ❌ 不推荐(在 .d.ts 中可能导致问题)
function add(a: number, b: number): number
class User {
name: string
}2
3
4
5
6
7
8
9
10
11
分离类型定义
将类型定义和实现分离,便于维护和理解。
export interface User {
id: string
name: string
}
export interface Config {
apiUrl: string
}2
3
4
5
6
7
8
import type { User, Config } from '../types'
export function createUser(name: string): User {
return { id: '1', name }
}2
3
4
5
使用命名空间组织复杂类型
// types.d.ts
declare namespace API {
interface User {
id: string
name: string
}
interface Post {
id: string
title: string
author: User
}
namespace Request {
interface GetUser {
userId: string
}
interface CreatePost {
title: string
content: string
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
为第三方库创建独立的类型文件
types/
├── express.d.ts
├── lodash.d.ts
└── my-custom-lib.d.ts2
3
4
使用三斜线指令引用其他声明文件
// types.d.ts
/// <reference types="node" />
/// <reference path="./custom.d.ts" />
declare module 'my-module' {
// 可以使用 node 和 custom.d.ts 中的类型
}2
3
4
5
6
7
避免使用 any
在类型声明中尽量避免使用 any,使用更具体的类型。
// ❌ 不推荐
declare function process(data: any): any
// ✅ 推荐
declare function process<T>(data: T): T
// ✅ 或使用 unknown
declare function process(data: unknown): unknown2
3
4
5
6
7
8
提供完整的文档注释
// types.d.ts
/**
* 创建新用户
* @param name - 用户名
* @param age - 用户年龄
* @returns 用户对象
* @example
* ```ts
* const user = createUser('Alice', 25)
* ```
*/
declare function createUser(name: string, age: number): User2
3
4
5
6
7
8
9
10
11
12