Ember组件状态 #
一、响应式系统概述 #
Ember Octane使用细粒度响应式系统,基于 @tracked 装饰器实现自动追踪和更新。
1.1 响应式原理 #
text
状态变化 → 自动追踪 → 触发更新 → 重新渲染
1.2 与传统Ember对比 #
| 特性 | Classic Ember | Octane Ember |
|---|---|---|
| 状态定义 | EmberObject.extend() |
原生类 + @tracked |
| 计算属性 | computed() |
getter |
| 观察者 | observer() |
自动追踪 |
| 更新方式 | set() |
直接赋值 |
二、Tracked属性 #
2.1 基本用法 #
javascript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class CounterComponent extends Component {
@tracked count = 0;
increment() {
this.count++; // 直接赋值,自动触发更新
}
}
2.2 追踪对象属性 #
javascript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class UserFormComponent extends Component {
@tracked firstName = '';
@tracked lastName = '';
@tracked email = '';
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
2.3 追踪复杂对象 #
javascript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
// 使用TrackedObject
import { tracked } from '@glimmer/tracking';
class User {
@tracked name;
@tracked email;
constructor(name, email) {
this.name = name;
this.email = email;
}
}
export default class UserListComponent extends Component {
@tracked users = [];
addUser(name, email) {
this.users = [...this.users, new User(name, email)];
}
}
三、计算属性 #
3.1 Getter作为计算属性 #
javascript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class ShoppingCartComponent extends Component {
@tracked items = [];
get total() {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
get itemCount() {
return this.items.reduce((sum, item) => sum + item.quantity, 0);
}
get isEmpty() {
return this.items.length === 0;
}
get formattedTotal() {
return `¥${this.total.toFixed(2)}`;
}
}
3.2 自动依赖追踪 #
javascript
export default class UserProfileComponent extends Component {
@tracked firstName = '';
@tracked lastName = '';
get fullName() {
// 自动追踪 firstName 和 lastName
return `${this.firstName} ${this.lastName}`;
}
get initials() {
// 自动追踪 fullName,间接追踪 firstName 和 lastName
return this.fullName
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase();
}
}
3.3 缓存行为 #
计算属性默认是惰性求值且有缓存:
javascript
export default class ExpensiveComponent extends Component {
@tracked data = [];
get processedData() {
console.log('计算中...'); // 只在依赖变化时执行
return this.data.map(/* 复杂处理 */);
}
}
四、状态更新 #
4.1 数组更新 #
javascript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class TodoListComponent extends Component {
@tracked todos = [];
@action
addTodo(title) {
// 创建新数组触发更新
this.todos = [...this.todos, { id: Date.now(), title, completed: false }];
}
@action
removeTodo(id) {
this.todos = this.todos.filter((todo) => todo.id !== id);
}
@action
toggleTodo(id) {
this.todos = this.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
);
}
}
4.2 对象更新 #
javascript
export default class UserFormComponent extends Component {
@tracked user = { name: '', email: '', age: 0 };
@action
updateName(name) {
// 创建新对象触发更新
this.user = { ...this.user, name };
}
@action
updateEmail(email) {
this.user = { ...this.user, email };
}
}
4.3 使用TrackedObject和TrackedArray #
javascript
import { tracked } from '@glimmer/tracking';
import { TrackedObject, TrackedArray } from 'tracked-built-ins';
export default class AdvancedComponent extends Component {
@tracked user = new TrackedObject({
name: '',
email: '',
});
@tracked items = new TrackedArray([]);
@action
updateName(name) {
// 直接修改,自动追踪
this.user.name = name;
}
@action
addItem(item) {
// 直接push,自动追踪
this.items.push(item);
}
}
五、私有状态与公共状态 #
5.1 私有状态 #
组件内部管理的状态:
javascript
export default class DropdownComponent extends Component {
@tracked isOpen = false; // 私有状态
@action
toggle() {
this.isOpen = !this.isOpen;
}
@action
close() {
this.isOpen = false;
}
}
5.2 公共状态 #
通过参数传入的状态:
javascript
export default class AccordionComponent extends Component {
// 公共状态由父组件管理
// @args.openIndex
}
5.3 受控组件模式 #
javascript
// 受控组件 - 状态由父组件管理
export default class ControlledInputComponent extends Component {
get value() {
return this.args.value ?? '';
}
@action
handleInput(event) {
this.args.onChange?.(event.target.value);
}
}
handlebars
{{! 使用受控组件}}
<ControlledInput
@value={{this.searchTerm}}
@onChange={{this.updateSearchTerm}}
/>
5.4 非受控组件模式 #
javascript
// 非受控组件 - 状态由组件自己管理
export default class UncontrolledInputComponent extends Component {
@tracked value = this.args.defaultValue ?? '';
@action
handleInput(event) {
this.value = event.target.value;
}
}
六、状态持久化 #
6.1 使用服务持久化 #
javascript
// app/services/cart.js
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
export default class CartService extends Service {
@tracked items = [];
add(item) {
this.items = [...this.items, item];
}
remove(id) {
this.items = this.items.filter((item) => item.id !== id);
}
}
javascript
// app/components/cart-icon.js
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class CartIconComponent extends Component {
@service cart;
get itemCount() {
return this.cart.items.length;
}
}
6.2 本地存储持久化 #
javascript
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
const STORAGE_KEY = 'app_state';
export default class StateService extends Service {
@tracked data;
constructor() {
super(...arguments);
this.loadFromStorage();
}
loadFromStorage() {
const stored = localStorage.getItem(STORAGE_KEY);
this.data = stored ? JSON.parse(stored) : {};
}
saveToStorage() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.data));
}
set(key, value) {
this.data = { ...this.data, [key]: value };
this.saveToStorage();
}
}
七、状态管理最佳实践 #
7.1 状态提升 #
javascript
// 将共享状态提升到共同父组件
export default class ParentComponent extends Component {
@tracked selectedUserId = null;
@action
selectUser(id) {
this.selectedUserId = id;
}
}
handlebars
{{! 父组件模板}}
<UserList @users={{@users}} @selectedId={{this.selectedUserId}} @onSelect={{this.selectUser}} />
<UserDetail @user={{this.selectedUser}} />
7.2 单向数据流 #
handlebars
{{! 数据向下传递}}
<TodoList @todos={{this.todos}} />
{{! 动作向上传递}}
<TodoList @todos={{this.todos}} @onToggle={{this.toggleTodo}} @onDelete={{this.deleteTodo}} />
7.3 不可变更新 #
javascript
// 好的做法 - 不可变更新
@action
addItem(item) {
this.items = [...this.items, item];
}
// 避免 - 直接修改
@action
addItem(item) {
this.items.push(item); // 不会触发更新
}
7.4 状态初始化 #
javascript
export default class FormComponent extends Component {
// 使用getter进行延迟初始化
get formData() {
return {
name: this.args.user?.name ?? '',
email: this.args.user?.email ?? '',
};
}
}
八、调试状态 #
8.1 使用log助手 #
handlebars
{{log "当前状态:" this.todos}}
8.2 使用Ember Inspector #
Ember Inspector可以查看:
- 组件状态
- 追踪属性
- 计算属性缓存
8.3 状态变化追踪 #
javascript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class DebugComponent extends Component {
@tracked count = 0;
set count(value) {
console.log(`count changed: ${this.count} -> ${value}`);
this._count = value;
}
get count() {
return this._count;
}
}
九、总结 #
Ember状态管理要点:
| 概念 | 说明 |
|---|---|
| @tracked | 追踪属性变化 |
| getter | 计算属性 |
| 不可变更新 | 创建新对象/数组 |
| 状态提升 | 共享状态上移 |
| 单向数据流 | 数据向下,动作向上 |
掌握响应式状态管理是构建复杂应用的关键。
最后更新:2026-03-28