Ember嵌套路由 #

一、嵌套路由概述 #

嵌套路由允许你构建层次化的应用结构,父路由的模板包含子路由的出口。

1.1 基本概念 #

text
/posts           → 父路由
/posts/new       → 子路由
/posts/123       → 子路由
/posts/123/edit  → 更深层子路由

1.2 路由结构图 #

text
application
├── home
├── about
└── posts
    ├── new
    └── show
        └── edit

二、定义嵌套路由 #

2.1 路由配置 #

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

2.2 生成的路由 #

路由名称 URL 文件
posts /posts routes/posts.js
posts.new /posts/new routes/posts/new.js
posts.show /posts/:post_id routes/posts/show.js
posts.edit /posts/:post_id/edit routes/posts/edit.js

2.3 生成嵌套路由 #

bash
# 生成posts路由
ember generate route posts

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

三、模板嵌套 #

3.1 父路由模板 #

handlebars
{{! app/templates/posts.hbs}}
<div class="posts-layout">
  <aside class="sidebar">
    <h2>文章管理</h2>
    <nav>
      <LinkTo @route="posts.new">新建文章</LinkTo>
      <LinkTo @route="posts">文章列表</LinkTo>
    </nav>
  </aside>

  <main class="content">
    {{outlet}}
  </main>
</div>

3.2 子路由模板 #

handlebars
{{! app/templates/posts/index.hbs}}
<h1>文章列表</h1>
<ul>
  {{#each @model as |post|}}
    <li>
      <LinkTo @route="posts.show" @model={{post.id}}>
        {{post.title}}
      </LinkTo>
    </li>
  {{/each}}
</ul>
handlebars
{{! app/templates/posts/new.hbs}}
<h1>新建文章</h1>
<PostForm @onSubmit={{this.createPost}} />
handlebars
{{! app/templates/posts/show.hbs}}
<h1>{{@model.title}}</h1>
<p>{{@model.body}}</p>
<LinkTo @route="posts.edit" @model={{@model.id}}>编辑</LinkTo>

四、模型继承 #

4.1 父路由模型 #

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

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

4.2 子路由访问父模型 #

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

export default class PostsIndexRoute extends Route {
  // 继承父路由的模型
  // 不需要定义model方法
}
handlebars
{{! app/templates/posts/index.hbs}}
{{! 直接使用父路由的模型}}
{{#each @model as |post|}}
  <li>{{post.title}}</li>
{{/each}}

4.3 子路由独立模型 #

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);
  }
}

五、多层嵌套 #

5.1 三层嵌套 #

javascript
// app/router.js
Router.map(function () {
  this.route('admin', function () {
    this.route('users', function () {
      this.route('show', { path: '/:user_id' });
      this.route('edit', { path: '/:user_id/edit' });
    });
  });
});

5.2 模板结构 #

handlebars
{{! app/templates/admin.hbs}}
<div class="admin-layout">
  <AdminSidebar />
  {{outlet}}
</div>
handlebars
{{! app/templates/admin/users.hbs}}
<div class="users-layout">
  <UsersList @users={{@model}} />
  {{outlet}}
</div>
handlebars
{{! app/templates/admin/users/show.hbs}}
<UserDetail @user={{@model}} />

5.3 导航链接 #

handlebars
{{! 三层嵌套链接}}
<LinkTo @route="admin.users.show" @model={{user.id}}>
  查看用户
</LinkTo>

六、动态嵌套 #

6.1 动态段嵌套 #

javascript
Router.map(function () {
  this.route('categories', function () {
    this.route('category', { path: '/:category_id', resetNamespace: true }, function () {
      this.route('products');
      this.route('product', { path: '/products/:product_id' });
    });
  });
});

6.2 模型钩子链 #

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

export default class CategoryRoute extends Route {
  model(params) {
    return this.store.findRecord('category', params.category_id);
  }
}
javascript
// app/routes/category/products.js
import Route from '@ember/routing/route';

export default class CategoryProductsRoute extends Route {
  model() {
    // 访问父路由模型
    const category = this.modelFor('category');
    return this.store.query('product', { categoryId: category.id });
  }
}

七、resetNamespace选项 #

7.1 默认命名空间 #

javascript
Router.map(function () {
  this.route('admin', function () {
    this.route('users'); // 路由名: admin.users
  });
});

7.2 重置命名空间 #

javascript
Router.map(function () {
  this.route('admin', function () {
    this.route('users', { resetNamespace: true }); // 路由名: users
  });
});

八、最佳实践 #

8.1 合理的嵌套层级 #

javascript
// 好的做法 - 2-3层嵌套
Router.map(function () {
  this.route('posts', function () {
    this.route('show', { path: '/:post_id' });
  });
});

// 避免 - 过深嵌套
Router.map(function () {
  this.route('a', function () {
    this.route('b', function () {
      this.route('c', function () {
        this.route('d', function () {
          // 太深了
        });
      });
    });
  });
});

8.2 共享布局 #

handlebars
{{! 父路由模板提供共享布局}}
<div class="app-layout">
  <Sidebar />
  <main>
    {{outlet}}
  </main>
</div>

8.3 模型复用 #

javascript
// 父路由加载共享数据
export default class AdminRoute extends Route {
  model() {
    return {
      users: this.store.findAll('user'),
      settings: this.store.findRecord('settings', 'current'),
    };
  }
}

九、总结 #

嵌套路由要点:

概念 说明
function() { } 定义嵌套路由
{{outlet}} 渲染子路由模板
modelFor() 访问父路由模型
resetNamespace 重置命名空间

合理使用嵌套路由可以构建清晰的应用结构。

最后更新:2026-03-28