前端依赖注入:解耦组件依赖

前端依赖注入:解耦组件依赖 前端依赖注入解耦组件依赖前言各位前端小伙伴不知道你们有没有遇到过这种情况组件之间依赖关系复杂难以测试和维护我曾经开发过一个大型前端项目组件之间直接依赖修改一个组件会影响多个其他组件。后来我引入了依赖注入代码变得清晰易维护依赖注入核心概念什么是依赖注入依赖注入是一种设计模式它允许对象接收它所依赖的对象而不是自己创建它们。依赖注入的优势解耦组件组件之间不需要直接依赖具体实现易于测试可以轻松替换依赖进行测试提高复用性依赖可以被多个组件共享易于维护修改依赖不会影响使用它的组件依赖注入结构┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Container │ │ Provider │ │ Consumer │ │ (容器) │ │ (提供者) │ │ (消费者) │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ │ 1. 注册依赖 │ │ │───────────────────────│ │ │ │ │ │ │ 2. 获取依赖 │ │ │───────────────────────│ │ │ │ │ │ │ │ 3. 使用依赖 │ │ │────────────────────────│依赖注入实现基础实现class Container { constructor() { this.dependencies {} this.singletons {} } register(key, factory, isSingleton true) { this.dependencies[key] { factory, isSingleton } } resolve(key) { const { factory, isSingleton } this.dependencies[key] if (!factory) { throw new Error(Dependency ${key} not found) } if (isSingleton) { if (!this.singletons[key]) { this.singletons[key] factory(this) } return this.singletons[key] } return factory(this) } inject(target) { const injectables target.inject || [] const dependencies injectables.map(key this.resolve(key)) return new target(...dependencies) } } // 使用 const container new Container() container.register(api, () new ApiService()) container.register(logger, () new Logger()) class UserService { static inject [api, logger] constructor(api, logger) { this.api api this.logger logger } } const userService container.inject(UserService)装饰器实现const container new Container() function inject(key) { return function(target, propertyKey) { Object.defineProperty(target, propertyKey, { get() { return container.resolve(key) }, configurable: true }) } } class UserService { inject(api) api inject(logger) logger getUser(id) { this.logger.log(Getting user:, id) return this.api.get(/users/${id}) } }依赖注入在前端框架中的应用React中的依赖注入import { createContext, useContext, useMemo } from react const ContainerContext createContext(null) export function ContainerProvider({ children, container }) { return ( ContainerContext.Provider value{container} {children} /ContainerContext.Provider ) } export function useInject(key) { const container useContext(ContainerContext) return useMemo(() container.resolve(key), [container, key]) } // 使用 const container new Container() container.register(api, () new ApiService()) function App() { return ( ContainerProvider container{container} UserProfile / /ContainerProvider ) } function UserProfile() { const api useInject(api) useEffect(() { api.getUser(1).then(setUser) }, [api]) return divUser Profile/div }Vue中的依赖注入import { provide, inject, createApp } from vue const container new Container() container.register(api, () new ApiService()) const app createApp(App) app.provide(container, container) // 自定义hook function useInject(key) { const container inject(container) return container.resolve(key) } // 使用 function UserProfile() { const api useInject(api) onMounted(() { api.getUser(1).then(setUser) }) return divUser Profile/div }依赖注入高级功能1. 依赖作用域class Container { constructor(parent null) { this.dependencies {} this.singletons {} this.parent parent } createChild() { return new Container(this) } resolve(key) { if (this.dependencies[key]) { const { factory, isSingleton } this.dependencies[key] if (isSingleton !this.singletons[key]) { this.singletons[key] factory(this) } return isSingleton ? this.singletons[key] : factory(this) } if (this.parent) { return this.parent.resolve(key) } throw new Error(Dependency ${key} not found) } }2. 依赖工厂class Container { constructor() { this.factories {} } register(key, factory) { this.factories[key] factory } registerInstance(key, instance) { this.factories[key] () instance } registerFactory(key, FactoryClass) { this.factories[key] (container) new FactoryClass(container) } resolve(key) { const factory this.factories[key] if (!factory) { throw new Error(Dependency ${key} not found) } return factory(this) } }3. 依赖生命周期class Container { constructor() { this.dependencies {} this.singletons {} this.disposables [] } register(key, factory, options {}) { this.dependencies[key] { factory, ...options } } resolve(key) { const { factory, singleton true, onDispose } this.dependencies[key] if (!factory) { throw new Error(Dependency ${key} not found) } if (singleton) { if (!this.singletons[key]) { this.singletons[key] factory(this) if (onDispose) { this.disposables.push(() onDispose(this.singletons[key])) } } return this.singletons[key] } const instance factory(this) if (onDispose) { this.disposables.push(() onDispose(instance)) } return instance } dispose() { this.disposables.forEach(dispose dispose()) this.singletons {} this.disposables [] } }依赖注入最佳实践1. 定义依赖接口interface ApiService { get(url: string): Promiseany post(url: string, data: any): Promiseany } class RealApiService implements ApiService { get(url: string) { return fetch(url).then(res res.json()) } post(url: string, data: any) { return fetch(url, { method: POST, body: JSON.stringify(data) }).then(res res.json()) } } class MockApiService implements ApiService { get(url: string) { return Promise.resolve({ mock: true }) } post(url: string, data: any) { return Promise.resolve({ success: true }) } }2. 配置环境依赖const container new Container() if (process.env.NODE_ENV production) { container.register(api, () new RealApiService()) } else { container.register(api, () new MockApiService()) }3. 单元测试import { Container } from ./container import { UserService } from ./userService describe(UserService, () { let container let mockApi beforeEach(() { container new Container() mockApi { get: jest.fn().mockResolvedValue({ id: 1, name: John }) } container.register(api, () mockApi) }) it(should get user, async () { const userService container.inject(UserService) const user await userService.getUser(1) expect(mockApi.get).toHaveBeenCalledWith(/users/1) expect(user).toEqual({ id: 1, name: John }) }) })依赖注入vs其他模式依赖注入vs服务定位器特性依赖注入服务定位器耦合度低中可测试性高中显式依赖是否灵活性高中依赖注入vs构造器注入特性依赖注入构造器注入自动化自动手动灵活性高低复杂度中低适用场景复杂应用简单应用依赖注入常见问题问题1过度使用解决方案只在需要解耦的地方使用简单场景直接实例化遵循YAGNI原则问题2依赖链过长解决方案使用工厂模式简化使用组合模式定期审查依赖关系问题3难以追踪依赖解决方案使用类型系统添加文档注释使用可视化工具总结依赖注入是解耦组件依赖的利器解耦组件组件之间不需要直接依赖具体实现易于测试可以轻松替换依赖进行测试提高复用性依赖可以被多个组件共享易于维护修改依赖不会影响使用它的组件现在开始使用依赖注入构建更灵活的前端应用吧你的代码会感谢你的最后一句忠告不要过度使用依赖注入简单场景直接实例化更合适