Ember依赖注入 #
一、依赖注入概述 #
Ember使用依赖注入(DI)模式来管理组件和服务之间的依赖关系。
1.1 DI的优势 #
- 解耦组件和服务
- 便于测试
- 单例管理
- 延迟初始化
1.2 DI架构 #
text
┌─────────────────────────────────────────────────────────┐
│ Application │
├─────────────────────────────────────────────────────────┤
│ Registry │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Service A│ │Service B│ │Service C│ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ ▲ ▲ ▲ │
│ │ │ │ │
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │
│ │Component│ │Component│ │Controller│ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────┘
二、服务注入 #
2.1 基本注入 #
javascript
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class MyComponent extends Component {
@service session;
@service store;
@service router;
}
2.2 注入别名 #
javascript
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class MyComponent extends Component {
@service('session') auth;
@service('shopping-cart') cart;
}
2.3 注入选项 #
javascript
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class MyComponent extends Component {
@service({
name: 'session',
as: 'auth',
})
session;
}
三、注入目标 #
3.1 组件注入 #
javascript
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class UserAvatarComponent extends Component {
@service session;
get user() {
return this.session.currentUser;
}
}
3.2 控制器注入 #
javascript
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ApplicationController extends Controller {
@service session;
@service theme;
}
3.3 路由注入 #
javascript
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default class ApplicationRoute extends Route {
@service session;
@service notifications;
}
3.4 服务注入服务 #
javascript
import Service from '@ember/service';
import { inject as service } from '@ember/service';
export default class ApiService extends Service {
@service session;
@service config;
get headers() {
return {
Authorization: `Bearer ${this.session.token}`,
'X-API-Key': this.config.apiKey,
};
}
}
四、初始化器 #
4.1 创建初始化器 #
bash
ember generate initializer session
4.2 注册服务 #
javascript
// app/initializers/session.js
export function initialize(application) {
application.register('service:session', SessionService);
}
export default {
name: 'session',
initialize,
};
4.3 注入服务 #
javascript
// app/initializers/session.js
export function initialize(application) {
// 注册服务
application.register('service:session', SessionService);
// 自动注入到所有组件
application.inject('component', 'session', 'service:session');
// 自动注入到所有路由
application.inject('route', 'session', 'service:session');
// 自动注入到所有控制器
application.inject('controller', 'session', 'service:session');
}
export default {
name: 'session',
initialize,
};
五、实例初始化器 #
5.1 创建实例初始化器 #
bash
ember generate instance-initializer session
5.2 实例初始化器 #
javascript
// app/instance-initializers/session.js
export function initialize(applicationInstance) {
const session = applicationInstance.lookup('service:session');
// 从本地存储恢复会话
session.restore();
}
export default {
name: 'session',
initialize,
};
5.3 初始化器与实例初始化器区别 #
| 类型 | 执行时机 | 用途 |
|---|---|---|
| Initializer | 应用启动时 | 注册、全局注入 |
| Instance Initializer | 实例创建时 | 查找服务、初始化状态 |
六、查找服务 #
6.1 使用lookup #
javascript
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default class ApplicationRoute extends Route {
@service session;
beforeModel() {
// 手动查找服务
const store = this.owner.lookup('service:store');
const config = this.owner.lookup('service:config');
}
}
6.2 在组件中查找 #
javascript
import Component from '@glimmer/component';
import { getOwner } from '@ember/application';
export default class MyComponent extends Component {
constructor(owner, args) {
super(owner, args);
// 获取owner
const appOwner = getOwner(this);
// 查找服务
const session = appOwner.lookup('service:session');
}
}
七、依赖关系 #
7.1 服务依赖链 #
javascript
// app/services/api.js
import Service from '@ember/service';
import { inject as service } from '@ember/service';
export default class ApiService extends Service {
@service session;
@service config;
}
// app/services/auth.js
import Service from '@ember/service';
import { inject as service } from '@ember/service';
export default class AuthService extends Service {
@service api;
@service session;
}
// app/services/user.js
import Service from '@ember/service';
import { inject as service } from '@ember/service';
export default class UserService extends Service {
@service auth;
@service store;
}
7.2 循环依赖 #
避免循环依赖:
javascript
// 错误 - 循环依赖
// Service A → Service B → Service A
// 解决方案:使用事件或共享状态
八、测试与DI #
8.1 注入测试服务 #
javascript
// tests/integration/components/my-component-test.js
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | my-component', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
// 注册测试服务
this.owner.register('service:session', MockSessionService);
});
test('it renders', async function (assert) {
await render(hbs`<MyComponent />`);
assert.dom(this.element).hasText('');
});
});
8.2 查找服务进行测试 #
javascript
test('uses session service', async function (assert) {
const session = this.owner.lookup('service:session');
session.login({ name: 'Test User' });
await render(hbs`<UserMenu />`);
assert.dom().includesText('Test User');
});
九、最佳实践 #
9.1 显式注入 #
javascript
// 好的做法 - 显式注入
export default class MyComponent extends Component {
@service session;
@service store;
}
// 避免 - 全局注入过多
// 只在初始化器中注入真正需要全局访问的服务
9.2 避免过度注入 #
javascript
// 好的做法 - 按需注入
export default class MyComponent extends Component {
@service session;
}
// 避免 - 注入不需要的服务
export default class MyComponent extends Component {
@service session;
@service store;
@service router;
@service theme;
// ...
}
9.3 服务命名 #
javascript
// 好的命名
@service session;
@service shoppingCart;
@service apiClient;
// 避免
@service s;
@service cart;
十、总结 #
依赖注入要点:
| 概念 | 说明 |
|---|---|
| @inject | 注入装饰器 |
| lookup | 手动查找 |
| Initializer | 应用初始化 |
| Instance Initializer | 实例初始化 |
理解依赖注入是掌握Ember架构的关键。
最后更新:2026-03-28