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