Ember服务基础 #

一、服务概述 #

Service是Ember中的单例对象,用于在整个应用中共享功能和状态。

1.1 服务特点 #

  • 单例模式:整个应用只有一个实例
  • 依赖注入:通过装饰器注入到需要的地方
  • 生命周期:与应用生命周期一致
  • 响应式:支持@tracked追踪

1.2 常见用途 #

用途 示例
状态管理 用户登录状态、主题设置
API通信 HTTP请求封装
本地存储 localStorage封装
工具函数 日期格式化、验证

二、创建服务 #

2.1 生成服务 #

bash
ember generate service session
ember generate service theme
ember generate service api

2.2 基本服务 #

javascript
// app/services/session.js
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class SessionService extends Service {
  @tracked isAuthenticated = false;
  @tracked currentUser = null;

  login(user) {
    this.isAuthenticated = true;
    this.currentUser = user;
  }

  logout() {
    this.isAuthenticated = false;
    this.currentUser = null;
  }
}

三、注入服务 #

3.1 在组件中注入 #

javascript
// app/components/user-menu.js
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

export default class UserMenuComponent extends Component {
  @service session;

  @action
  logout() {
    this.session.logout();
  }
}
handlebars
{{! app/components/user-menu.hbs}}
{{#if this.session.isAuthenticated}}
  <p>欢迎,{{this.session.currentUser.name}}</p>
  <button {{on "click" this.logout}}>退出</button>
{{else}}
  <LinkTo @route="login">登录</LinkTo>
{{/if}}

3.2 在路由中注入 #

javascript
// app/routes/admin.js
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class AdminRoute extends Route {
  @service session;
  @service router;

  beforeModel(transition) {
    if (!this.session.isAuthenticated) {
      this.router.transitionTo('login');
    }
  }
}

3.3 在控制器中注入 #

javascript
// app/controllers/posts.js
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class PostsController extends Controller {
  @service store;
  @service session;

  @tracked posts = [];

  @action
  async loadPosts() {
    this.posts = await this.store.findAll('post');
  }
}

3.4 在其他服务中注入 #

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;

  async request(url, options = {}) {
    const headers = {
      'Content-Type': 'application/json',
      ...options.headers,
    };

    if (this.session.isAuthenticated) {
      headers.Authorization = `Bearer ${this.session.token}`;
    }

    const response = await fetch(url, {
      ...options,
      headers,
    });

    return response.json();
  }
}

四、服务属性 #

4.1 追踪属性 #

javascript
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class ThemeService extends Service {
  @tracked current = 'light';

  get isDark() {
    return this.current === 'dark';
  }

  toggle() {
    this.current = this.isDark ? 'light' : 'dark';
  }
}

4.2 计算属性 #

javascript
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class CartService extends Service {
  @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;
  }
}

五、服务方法 #

5.1 基本方法 #

javascript
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class NotificationService extends Service {
  @tracked notifications = [];

  add(message, type = 'info') {
    const notification = {
      id: Date.now(),
      message,
      type,
      timestamp: new Date(),
    };

    this.notifications = [...this.notifications, notification];

    return notification;
  }

  remove(id) {
    this.notifications = this.notifications.filter((n) => n.id !== id);
  }

  clear() {
    this.notifications = [];
  }

  success(message) {
    return this.add(message, 'success');
  }

  error(message) {
    return this.add(message, 'error');
  }

  warning(message) {
    return this.add(message, 'warning');
  }
}

5.2 异步方法 #

javascript
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class AuthService extends Service {
  @tracked isAuthenticated = false;
  @tracked currentUser = null;
  @tracked isLoading = false;

  async login(email, password) {
    this.isLoading = true;

    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password }),
      });

      if (!response.ok) {
        throw new Error('登录失败');
      }

      const user = await response.json();
      this.currentUser = user;
      this.isAuthenticated = true;

      return user;
    } finally {
      this.isLoading = false;
    }
  }

  async logout() {
    await fetch('/api/logout', { method: 'POST' });
    this.currentUser = null;
    this.isAuthenticated = false;
  }
}

六、服务生命周期 #

6.1 构造函数 #

javascript
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class StorageService extends Service {
  @tracked data = {};

  constructor() {
    super(...arguments);
    this.loadFromStorage();
  }

  loadFromStorage() {
    const stored = localStorage.getItem('app_data');
    if (stored) {
      this.data = JSON.parse(stored);
    }
  }

  saveToStorage() {
    localStorage.setItem('app_data', JSON.stringify(this.data));
  }

  set(key, value) {
    this.data = { ...this.data, [key]: value };
    this.saveToStorage();
  }

  get(key) {
    return this.data[key];
  }

  willDestroy() {
    this.saveToStorage();
    super.willDestroy(...arguments);
  }
}

七、服务别名 #

7.1 使用别名 #

javascript
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';

export default class MyComponent extends Component {
  @service('session') auth; // 使用别名

  get isLoggedIn() {
    return this.auth.isAuthenticated;
  }
}

八、最佳实践 #

8.1 单一职责 #

javascript
// 好的做法 - 单一职责
export default class SessionService extends Service {
  // 只处理用户会话
}

export default class ThemeService extends Service {
  // 只处理主题
}

// 避免 - 职责过多
export default class AppService extends Service {
  // 处理会话、主题、通知、购物车...
}

8.2 命名约定 #

javascript
// 好的命名
@service session;
@service theme;
@service cart;
@service notifications;

// 避免
@service s;
@service t;

8.3 文档化 #

javascript
/**
 * Session服务
 * 
 * 管理用户会话状态
 * 
 * @property {boolean} isAuthenticated - 是否已认证
 * @property {User|null} currentUser - 当前用户
 * @method login - 登录
 * @method logout - 登出
 */
export default class SessionService extends Service {
}

九、总结 #

服务核心概念:

概念 说明
单例 应用唯一实例
@inject 依赖注入
@tracked 响应式状态
生命周期 constructor/willDestroy

服务是Ember应用中共享状态和功能的核心机制。

最后更新:2026-03-28