Ember路由基础 #

一、路由概述 #

Ember的路由系统是框架的核心特性之一,它将URL映射到应用状态,实现了URL驱动的应用架构。

1.1 路由的作用 #

  • 将URL映射到路由处理器
  • 加载模型数据
  • 渲染对应模板
  • 管理应用状态

1.2 路由架构 #

text
URL → Router → Route → Model → Controller → Template

二、路由配置 #

2.1 基本配置 #

javascript
// app/router.js
import EmberRouter from '@ember/routing/router';
import config from 'my-app/config/environment';

export default class Router extends EmberRouter {
  location = config.locationType;
  rootURL = config.rootURL;
}

Router.map(function () {
  this.route('home', { path: '/' });
  this.route('about');
  this.route('contact');
});

2.2 路由选项 #

javascript
Router.map(function () {
  // 基本路由
  this.route('about');

  // 自定义路径
  this.route('users', { path: '/people' });

  // 重定向路径
  this.route('blog', { path: '/posts' });

  // 带参数的路由
  this.route('post', { path: '/posts/:post_id' });
});

2.3 生成路由 #

bash
# 生成路由
ember generate route about

# 生成带嵌套路由
ember generate route posts/show

三、路由文件 #

3.1 基本路由 #

javascript
// app/routes/about.js
import Route from '@ember/routing/route';

export default class AboutRoute extends Route {
}

3.2 加载模型 #

javascript
// app/routes/posts.js
import Route from '@ember/routing/route';

export default class PostsRoute extends Route {
  async model() {
    const response = await fetch('/api/posts');
    return response.json();
  }
}

3.3 使用Ember Data #

javascript
// app/routes/posts.js
import Route from '@ember/routing/route';

export default class PostsRoute extends Route {
  model() {
    return this.store.findAll('post');
  }
}

3.4 加载单个模型 #

javascript
// app/routes/posts/show.js
import Route from '@ember/routing/route';

export default class PostsShowRoute extends Route {
  model(params) {
    return this.store.findRecord('post', params.post_id);
  }
}

四、路由钩子 #

4.1 钩子执行顺序 #

javascript
// app/routes/posts.js
import Route from '@ember/routing/route';

export default class PostsRoute extends Route {
  beforeModel(transition) {
    // 1. 模型加载前执行
    console.log('beforeModel');
  }

  async model(params, transition) {
    // 2. 加载模型
    console.log('model');
    return this.store.findAll('post');
  }

  afterModel(resolvedModel, transition) {
    // 3. 模型加载后执行
    console.log('afterModel');
  }

  setupController(controller, model) {
    // 4. 设置控制器
    super.setupController(controller, model);
    console.log('setupController');
  }

  renderTemplate(controller, model) {
    // 5. 渲染模板(可选覆盖)
    console.log('renderTemplate');
  }

  activate() {
    // 6. 路由激活
    console.log('activate');
  }

  deactivate() {
    // 路由离开时
    console.log('deactivate');
  }
}

4.2 beforeModel #

用于权限检查、重定向:

javascript
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) {
      // 保存目标URL
      this.session.set('attemptedTransition', transition);
      // 重定向到登录页
      this.router.transitionTo('login');
    }
  }
}

4.3 afterModel #

用于数据验证、条件重定向:

javascript
import Route from '@ember/routing/route';

export default class PostEditRoute extends Route {
  @service router;

  afterModel(post) {
    if (post.isPublished) {
      // 已发布的文章不能编辑,重定向到查看页
      this.router.transitionTo('posts.show', post.id);
    }
  }
}

五、导航链接 #

5.1 LinkTo组件 #

handlebars
{{! 基本链接}}
<LinkTo @route="about">关于我们</LinkTo>

{{! 带参数的链接}}
<LinkTo @route="posts.show" @model={{@post.id}}>
  查看文章
</LinkTo>

{{! 带查询参数}}
<LinkTo @route="posts" @query={{hash page=1 sort="date"}}>
  文章列表
</LinkTo>

{{! 多个模型参数}}
<LinkTo @route="posts.comments.show" @models={{array @post.id @comment.id}}>
  查看评论
</LinkTo>

5.2 LinkTo属性 #

handlebars
<LinkTo
  @route="posts"
  class="nav-link"
  id="posts-link"
  disabled={{this.isDisabled}}
>
  文章
</LinkTo>

5.3 激活状态 #

handlebars
{{! 激活时自动添加 active 类}}
<LinkTo @route="home" class="nav-link">
  首页
</LinkTo>

{{! 自定义激活类名}}
<LinkTo @route="home" @activeClass="current">
  首页
</LinkTo>

5.4 编程式导航 #

javascript
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

export default class PostsController extends Controller {
  @service router;

  @action
  goToPost(post) {
    this.router.transitionTo('posts.show', post.id);
  }

  @action
  goToPage(page) {
    this.router.transitionTo({ queryParams: { page } });
  }
}

六、查询参数 #

6.1 定义查询参数 #

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

export default class PostsController extends Controller {
  queryParams = ['page', 'sort', 'category'];

  @tracked page = 1;
  @tracked sort = 'date';
  @tracked category = null;
}

6.2 使用查询参数 #

handlebars
{{! 链接带查询参数}}
<LinkTo @route="posts" @query={{hash page=2 sort="title"}}>
  第2页
</LinkTo>

{{! 更新查询参数}}
<Input @value={{this.category}} {{on "input" this.updateCategory}} />

6.3 查询参数选项 #

javascript
// app/controllers/posts.js
import Controller from '@ember/controller';

export default class PostsController extends Controller {
  queryParams = [
    { page: { type: 'number' } },
    { sort: { replace: true } },
    { category: { as: 'cat' } },
  ];

  page = 1;
  sort = 'date';
  category = null;
}

6.4 查询参数刷新 #

javascript
// app/routes/posts.js
import Route from '@ember/routing/route';

export default class PostsRoute extends Route {
  queryParams = {
    page: { refreshModel: true },
    sort: { refreshModel: true },
    category: { refreshModel: true },
  };

  model(params) {
    return this.store.query('post', {
      page: params.page,
      sort: params.sort,
      category: params.category,
    });
  }
}

七、路由模板 #

7.1 基本模板 #

handlebars
{{! app/templates/posts.hbs}}
<h1>文章列表</h1>

<ul>
  {{#each @model as |post|}}
    <li>
      <LinkTo @route="posts.show" @model={{post.id}}>
        {{post.title}}
      </LinkTo>
    </li>
  {{/each}}
</ul>

{{outlet}}

7.2 应用模板 #

handlebars
{{! app/templates/application.hbs}}
<header>
  <nav>
    <LinkTo @route="home">首页</LinkTo>
    <LinkTo @route="posts">文章</LinkTo>
    <LinkTo @route="about">关于</LinkTo>
  </nav>
</header>

<main>
  {{outlet}}
</main>

<footer>
  <p>© 2024 My App</p>
</footer>

八、路由错误处理 #

8.1 错误子路由 #

javascript
// app/router.js
Router.map(function () {
  this.route('posts', function () {
    this.route('show', { path: '/:post_id' });
    this.route('error', { path: '/error' });
  });
});

8.2 错误处理 #

javascript
// app/routes/posts/show.js
import Route from '@ember/routing/route';
import { action } from '@ember/object';

export default class PostsShowRoute extends Route {
  model(params) {
    return this.store.findRecord('post', params.post_id);
  }

  @action
  error(error, transition) {
    if (error.status === 404) {
      this.router.transitionTo('posts.error');
    }
  }
}

8.3 加载子状态 #

javascript
// app/routes/posts.js
import Route from '@ember/routing/route';

export default class PostsRoute extends Route {
  async model() {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    return this.store.findAll('post');
  }
}
handlebars
{{! app/templates/posts-loading.hbs}}
<div class="loading">
  加载中...
</div>

九、路由最佳实践 #

9.1 路由命名 #

javascript
// 好的命名
this.route('posts');
this.route('posts/show');
this.route('posts/new');

// 避免
this.route('p');
this.route('viewPost');

9.2 模型加载 #

javascript
// 好的做法 - 在路由中加载模型
export default class PostsRoute extends Route {
  model() {
    return this.store.findAll('post');
  }
}

// 避免 - 在控制器中加载数据

9.3 权限控制 #

javascript
// 在beforeModel中统一处理
export default class AdminRoute extends Route {
  @service session;

  beforeModel(transition) {
    if (!this.session.isAdmin) {
      throw new Error('Unauthorized');
    }
  }
}

十、总结 #

Ember路由核心概念:

概念 说明
Router URL到路由的映射
Route 加载模型、处理逻辑
LinkTo 声明式导航链接
queryParams URL查询参数
{{outlet}} 渲染嵌套路由

掌握路由系统是构建Ember应用的基础。

最后更新:2026-03-28