如何检测和解决项目中的循环依赖?
检测循环依赖
1. madge 工具
# 安装
npm install -g madge
# 检测循环依赖
madge --circular src/
# 输出示例
# 2 circular dependencies found!
# 1) a.js → b.js → a.js
# 2) c.js → d.js → e.js → c.js2. Webpack 插件
const CircularDependencyPlugin = require('circular-dependency-plugin')
plugins: [
new CircularDependencyPlugin({
exclude: /node_modules/,
failOnError: false,
onStart({ compilation }) {
console.log('start detecting circular dependencies')
},
onDetected({ module, paths, compilation }) {
compilation.warnings.push(new Error(paths.join(' → ')))
}
})
]3. ESLint 规则
// eslint-plugin-import
'import/no-cycle': ['error', { maxDepth: 10 }]常见循环依赖模式
1. 直接循环
// a.js → b.js → a.js
// a.js
import { b } from './b.js'
// b.js
import { a } from './a.js'2. 间接循环
// a.js → b.js → c.js → a.js
// a.js
import { b } from './b.js'
// b.js
import { c } from './c.js'
// c.js
import { a } from './a.js'解决方案
1. 提取共享模块(推荐)
// 原来:A ↔ B
// a.js
import { getData } from './b.js'
export function processA() { return getData() }
// b.js
import { formatA } from './a.js'
export function getData() { return formatA(data) }
// 重构:提取共享部分
// shared.js
export function getData() { return data }
export function formatData(data) { return formatted }
// a.js → 只依赖 shared
// b.js → 只依赖 shared2. 依赖注入
// a.js
export function createA(b) {
return {
process() { return b.getData() }
}
}
// b.js
export function createB(a) {
return {
getData() { return a.format() }
}
}
// main.js
const a = createA(b) // 先创建一个引用
const b = createB(a)
a.b = b // 注入3. 事件总线
// eventBus.js(无依赖)
export const bus = new EventTarget()
// a.js
import { bus } from './eventBus.js'
bus.addEventListener('needData', (e) => {
e.detail.callback(getData())
})
// b.js
import { bus } from './eventBus.js'
function getDataFromA() {
return new Promise(resolve => {
bus.dispatchEvent(new CustomEvent('needData', {
detail: { callback: resolve }
}))
})
}4. 延迟导入
// a.js
export function processA() {
// 运行时导入,避免循环
const { getData } = require('./b.js')
return getData()
}5. 接口/类型提取
// types.js(纯类型,无实现)
export interface IB { getData(): string }
// a.js
import type { IB } from './types.js'
export function processA(b: IB) { return b.getData() }
// b.js
import { processA } from './a.js'
export class BImpl implements IB {
getData() { return 'data' }
}最佳实践
- 单向依赖:模块应该单向依赖,不互相引用
- 分层架构:上层依赖下层,下层不依赖上层
- 提取公共模块:循环部分提取到独立模块
- CI 检测:在 CI 流程中加入循环依赖检测
- Code Review:审查新增依赖关系