Ember组件生命周期 #
一、生命周期概述 #
Glimmer组件的生命周期比经典Ember组件更简单,主要包含两个阶段:
text
创建 → 运行 → 销毁
1.1 生命周期图 #
text
┌─────────────────────────────────────────────────┐
│ 组件生命周期 │
├─────────────────────────────────────────────────┤
│ │
│ constructor() │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ willRender() │ ← 可选 │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 渲染 │ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ didRender() │ ← 可选 │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 运行中 │ ← 状态更新触发重新渲染 │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ willDestroy() │
│ │
└─────────────────────────────────────────────────┘
二、constructor #
2.1 基本用法 #
javascript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { assert } from '@ember/debug';
export default class UserCardComponent extends Component {
@tracked isExpanded = false;
constructor(owner, args) {
super(owner, args);
// 参数验证
assert('user is required', this.args.user);
// 初始化状态
this.isExpanded = this.args.initiallyExpanded ?? false;
console.log('组件创建');
}
}
2.2 初始化时机 #
constructor在组件实例化时调用,此时:
this.args已可用- DOM尚未渲染
- 不应进行DOM操作
javascript
export default class MyComponent extends Component {
constructor(owner, args) {
super(owner, args);
// 可以访问args
console.log(this.args.title);
// 不能访问DOM
// this.element // undefined
}
}
三、willDestroy #
3.1 基本用法 #
javascript
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class TimerComponent extends Component {
intervalId;
constructor(owner, args) {
super(owner, args);
this.startTimer();
}
startTimer() {
this.intervalId = setInterval(() => {
console.log('tick');
}, 1000);
}
willDestroy() {
// 清理资源
if (this.intervalId) {
clearInterval(this.intervalId);
}
super.willDestroy(...arguments);
}
}
3.2 清理事件监听 #
javascript
export default class ResizeComponent extends Component {
@tracked width = window.innerWidth;
constructor(owner, args) {
super(owner, args);
window.addEventListener('resize', this.handleResize);
}
willDestroy() {
window.removeEventListener('resize', this.handleResize);
super.willDestroy(...arguments);
}
@action
handleResize() {
this.width = window.innerWidth;
}
}
四、渲染钩子 #
4.1 willRender #
在每次渲染前调用:
javascript
import Component from '@glimmer/component';
export default class LoggerComponent extends Component {
willRender() {
console.log('即将渲染');
}
}
4.2 didRender #
在每次渲染后调用:
javascript
import Component from '@glimmer/component';
export default class FocusComponent extends Component {
didRender() {
// DOM已可用
const input = document.querySelector('#my-input');
if (input && this.args.autoFocus) {
input.focus();
}
}
}
4.3 使用Modifier替代 #
推荐使用Modifier处理DOM操作:
javascript
// app/modifiers/autofocus.js
import { modifier } from 'ember-modifier';
export default modifier(function autofocus(element, [condition = true]) {
if (condition) {
element.focus();
}
});
handlebars
<input {{autofocus @shouldFocus}} />
五、资源管理 #
5.1 使用ember-resources #
bash
ember install ember-resources
javascript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { resource } from 'ember-resources';
export default class UserProfileComponent extends Component {
userData = resource(this, async () => {
const response = await fetch(`/api/users/${this.args.userId}`);
return response.json();
});
get user() {
return this.userData.value;
}
}
5.2 自定义资源 #
javascript
import { resource } from 'ember-resources';
export const RemoteData = resource(({ on }, url) => {
const controller = new AbortController();
on.cleanup(() => controller.abort());
return fetch(url, { signal: controller.signal }).then((r) => r.json());
});
六、常见生命周期场景 #
6.1 订阅/取消订阅 #
javascript
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class NotificationComponent extends Component {
@service notifications;
constructor(owner, args) {
super(owner, args);
this.notifications.subscribe(this.handleNotification);
}
willDestroy() {
this.notifications.unsubscribe(this.handleNotification);
super.willDestroy(...arguments);
}
@action
handleNotification(notification) {
// 处理通知
}
}
6.2 定时器 #
javascript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { later, cancel } from '@ember/runloop';
export default class CountdownComponent extends Component {
@tracked seconds = this.args.duration;
timer;
constructor(owner, args) {
super(owner, args);
this.startCountdown();
}
startCountdown() {
this.timer = later(() => {
if (this.seconds > 0) {
this.seconds--;
this.startCountdown();
}
}, 1000);
}
willDestroy() {
cancel(this.timer);
super.willDestroy(...arguments);
}
}
6.3 第三方库集成 #
javascript
import Component from '@glimmer/component';
import Chart from 'chart.js/auto';
export default class ChartComponent extends Component {
chart;
didRender() {
const canvas = document.getElementById('myChart');
if (canvas && !this.chart) {
this.chart = new Chart(canvas, {
type: 'line',
data: this.args.data,
options: this.args.options,
});
} else if (this.chart) {
this.chart.data = this.args.data;
this.chart.update();
}
}
willDestroy() {
if (this.chart) {
this.chart.destroy();
}
super.willDestroy(...arguments);
}
}
七、与Classic组件对比 #
7.1 生命周期对比 #
| Classic | Glimmer |
|---|---|
| init() | constructor() |
| didReceiveAttrs() | - (使用tracked) |
| willRender() | willRender() |
| didRender() | didRender() |
| didInsertElement() | - (使用Modifier) |
| willUpdate() | - |
| didUpdate() | - |
| willDestroyElement() | willDestroy() |
| didDestroyElement() | - |
7.2 迁移示例 #
javascript
// Classic组件
import Component from '@ember/component';
export default Component.extend({
didInsertElement() {
this._super(...arguments);
this.$('input').focus();
},
willDestroyElement() {
this._super(...arguments);
this.cleanup();
},
});
// Glimmer组件
import Component from '@glimmer/component';
export default class MyComponent extends Component {
willDestroy() {
this.cleanup();
super.willDestroy(...arguments);
}
}
handlebars
{{! 使用Modifier替代didInsertElement}}
<input {{autofocus}} />
八、最佳实践 #
8.1 使用Modifier处理DOM #
javascript
// 不推荐 - 在组件中操作DOM
export default class MyComponent extends Component {
didRender() {
document.querySelector('#input').focus();
}
}
// 推荐 - 使用Modifier
<input {{autofocus}} />
8.2 资源清理 #
javascript
// 好的做法 - 始终清理资源
export default class MyComponent extends Component {
constructor(owner, args) {
super(owner, args);
this.subscription = this.subscribe();
}
willDestroy() {
this.subscription?.unsubscribe();
super.willDestroy(...arguments);
}
}
8.3 避免在constructor中进行异步操作 #
javascript
// 不推荐
export default class MyComponent extends Component {
constructor(owner, args) {
super(owner, args);
this.loadData(); // 异步操作
}
}
// 推荐 - 使用资源或Modifier
export default class MyComponent extends Component {
@use data = RemoteData('/api/data');
}
九、总结 #
Glimmer组件生命周期要点:
| 钩子 | 用途 |
|---|---|
| constructor | 初始化状态 |
| willRender | 渲染前处理 |
| didRender | 渲染后DOM操作 |
| willDestroy | 清理资源 |
记住:
- 优先使用Modifier处理DOM操作
- 在willDestroy中清理资源
- 避免在constructor中进行异步操作
- 使用资源管理复杂状态
最后更新:2026-03-28