Ember动态路由 #

一、动态路由概述 #

动态路由允许URL包含可变部分,用于加载特定资源。

1.1 基本语法 #

javascript
Router.map(function () {
  // :post_id 是动态段
  this.route('post', { path: '/posts/:post_id' });
});

1.2 URL示例 #

URL 动态参数
/posts/1 post_id = "1"
/posts/abc post_id = "abc"
/posts/hello-world post_id = "hello-world"

二、定义动态路由 #

2.1 路由配置 #

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

  // 多个动态段
  this.route('user-post', { path: '/users/:user_id/posts/:post_id' });

  // 嵌套动态路由
  this.route('posts', function () {
    this.route('show', { path: '/:post_id' });
    this.route('edit', { path: '/:post_id/edit' });
  });
});

2.2 路由文件 #

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

export default class PostRoute extends Route {
  model(params) {
    // params.post_id 包含URL中的值
    return this.store.findRecord('post', params.post_id);
  }
}

三、模型钩子 #

3.1 model钩子参数 #

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

export default class PostsShowRoute extends Route {
  model(params, transition) {
    // params - 动态段参数
    console.log(params.post_id);

    // transition - 路由过渡信息
    console.log(transition.from);
    console.log(transition.to);

    return this.store.findRecord('post', params.post_id);
  }
}

3.2 异步模型加载 #

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

export default class PostRoute extends Route {
  async model(params) {
    const post = await this.store.findRecord('post', params.post_id);
    const comments = await this.store.query('comment', {
      postId: params.post_id,
    });

    return { post, comments };
  }
}

3.3 错误处理 #

javascript
import Route from '@ember/routing/route';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';

export default class PostRoute extends Route {
  @service router;

  async model(params) {
    try {
      return await this.store.findRecord('post', params.post_id);
    } catch (error) {
      if (error.errors?.[0]?.status === '404') {
        this.router.transitionTo('not-found');
      }
      throw error;
    }
  }

  @action
  error(error, transition) {
    console.error('路由错误:', error);
    return true; // 继续冒泡
  }
}

四、多个动态段 #

4.1 定义多动态段 #

javascript
Router.map(function () {
  this.route('user-post', { path: '/users/:user_id/posts/:post_id' });
});

4.2 访问多参数 #

javascript
// app/routes/user-post.js
import Route from '@ember/routing/route';

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

4.3 链接到多动态段 #

handlebars
<LinkTo
  @route="user-post"
  @models={{array user.id post.id}}
>
  查看文章
</LinkTo>

五、序列化与反序列化 #

5.1 serialize钩子 #

当使用模型对象生成URL时,Ember调用serialize:

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

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

  serialize(model) {
    // 将模型转换为URL参数
    return { post_id: model.id };
  }
}

5.2 默认行为 #

默认情况下,Ember使用动态段名称查找模型属性:

javascript
// 动态段 :post_id
// Ember 自动使用 model.id

// 等价于
serialize(model) {
  return { post_id: model.get('id') };
}

5.3 自定义序列化 #

javascript
export default class PostRoute extends Route {
  serialize(model) {
    // 使用slug代替id
    return { post_id: model.slug };
  }
}

六、链接到动态路由 #

6.1 使用模型 #

handlebars
{{! 传递模型对象}}
<LinkTo @route="post" @model={{@post}}>
  {{@post.title}}
</LinkTo>

6.2 使用ID #

handlebars
{{! 传递ID字符串}}
<LinkTo @route="post" @model={{@post.id}}>
  {{@post.title}}
</LinkTo>

6.3 多个模型 #

handlebars
{{! 使用@models数组}}
<LinkTo
  @route="user-post"
  @models={{array @user.id @post.id}}
>
  查看文章
</LinkTo>

七、动态路由与嵌套 #

7.1 嵌套动态路由 #

javascript
Router.map(function () {
  this.route('posts', function () {
    this.route('show', { path: '/:post_id' }, function () {
      this.route('comments');
      this.route('comment', { path: '/comments/:comment_id' });
    });
  });
});

7.2 访问父路由模型 #

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

export default class PostsShowCommentRoute extends Route {
  model(params) {
    // 获取父路由的模型
    const post = this.modelFor('posts.show');

    return this.store.findRecord('comment', params.comment_id, {
      adapterOptions: { postId: post.id },
    });
  }
}

八、通配符路由 #

8.1 定义通配符路由 #

javascript
Router.map(function () {
  // 匹配 /pages/任意路径
  this.route('page', { path: '/pages/*path' });
});

8.2 使用通配符参数 #

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

export default class PageRoute extends Route {
  model(params) {
    // params.path 包含完整路径
    console.log(params.path); // "docs/guide/intro"
    return this.store.queryRecord('page', { path: params.path });
  }
}

九、最佳实践 #

9.1 参数命名 #

javascript
// 好的命名 - 清晰表达含义
this.route('post', { path: '/posts/:post_id' });
this.route('user', { path: '/users/:user_id' });

// 避免 - 模糊的命名
this.route('item', { path: '/:id' });

9.2 模型验证 #

javascript
export default class PostRoute extends Route {
  @service router;

  async model(params) {
    // 验证ID格式
    if (!/^\d+$/.test(params.post_id)) {
      this.router.transitionTo('not-found');
      return;
    }

    return this.store.findRecord('post', params.post_id);
  }
}

9.3 加载状态 #

handlebars
{{! app/templates/post-loading.hbs}}
<div class="loading-spinner">
  加载中...
</div>

十、总结 #

动态路由要点:

概念 说明
:param_name 定义动态段
model(params) 访问动态参数
serialize(model) 模型转URL参数
modelFor(route) 访问父路由模型
@model={{obj}} 链接传递模型

动态路由是构建RESTful风格应用的关键。

最后更新:2026-03-28