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