Ember应用开发:路由与模型详解
1. npm和Bower安装
在
npm install
和
bower install
命令末尾使用
--save-dev
和
--save
选项,可将库名和版本的键值对添加到各自工具的JSON文件中。对于Bower,该JSON文件是
bower.json
;对于npm,则是
package.json
。
例如,在
bower.json
中添加的键值对如下:
{
"name": "tracker",
"dependencies": {
"ember": "~2.4.3"
}
}
bower.json
文件列出了
ember.js
及其最低版本号。列出的库和资源不会保存到开发项目仓库或版本控制系统中,只有
bower.json
文件会被保存。开发人员检出代码后,可运行
bower install
和
npm install
来创建本地开发环境。
挑战任务
-
青铜挑战:限制导入
:修改
ember-cli-build.js,仅导入collapse.js和transition.js文件。这样做会使vendor.js文件变小,同时NavBar组件仍可正常工作。操作步骤如下:
1. 找到dist/assets/vendor.js文件,记录其代码行数或文件大小。
2. 修改ember-cli-build.js文件。
3. 比较修改后vendor.js的新文件大小。 -
白银挑战:添加Font Awesome
:Font Awesome是一个用于在项目中添加常用图标的UI库,图标可像字体一样缩放。使用Ember CLI插件添加Font Awesome,并在
app/templates/application.hbs中添加一个图标。可查看该插件的GitHub仓库获取更多信息。 -
黄金挑战:自定义NavBar
:Bootstrap使用SCSS编写,大量使用了变量和函数。在项目中使用SCSS版本时,可控制库编译样式规则的方式,甚至可创建Bootstrap主题来修改默认变量。通过在
app/stylesheets/app.scss中添加或修改变量,更改NavBar的背景颜色、边框半径和内边距值。
2. 路由基础
路由就像交通警察,当用户访问特定URL时,路由会将用户引导至构成该页面的数据。与之前项目中为表单提交和按钮点击创建事件监听器类似,路由可看作是监听当前URL变化的事件监听器。
每个网站都使用某种形式的路由。例如,访问
www.bignerdranch.com/we-teach/
时,服务器会将
/we-teach/
路由映射到服务器上名为
we-teach
的文件夹中的HTML文件。其他服务器可能采用不同方式,如运行一个输出HTML的函数,而不是检索静态HTML文件。
Ember应用也能实现类似功能,且无需向服务器请求HTML。当应用需要切换到不同屏幕时,会使用新的路由名称更新URL。路由器是主应用对象的子对象,具有用于监听和处理URL变化的事件监听器。它使用新路由在路由表中查找,并找到对应的
Ember.Route
对象,然后调用该路由对象的一系列方法,开始获取下一个屏幕所需数据的过程,这个回调过程称为路由生命周期钩子。
2.1 Ember应用路由创建
创建路由是Ember开发的基础。Ember的命名约定假设你将创建与路由名称匹配的关联控制器和模板。例如,创建名为
sightings
的路由时,路由器会将
/sightings
请求映射到
SightingsRoute
,进而设置
SightingsController
,并最终渲染
app/templates/sightings.hbs
模板。
以下是Tracker应用所需的路由列表:
| 路由名称 | 路由路径 | 路由数据 |
| ---- | ---- | ---- |
| index | /index | 无数据 - 重定向到sightings |
| sightings | /sightings | 目击列表 |
| cryptids | /cryptids | 神秘生物列表 |
| witnesses | /witnesses | 目击者列表 |
| sighting | /sighting | 单个目击详情 |
| cryptid | /cryptid | 单个神秘生物详情 |
| witness | /witness | 单个目击者详情 |
| sightings index | /sightings/index | 目击列表着陆页 |
| sightings new | /sightings/new | 创建新目击的表单 |
| sighting index | /sighting/:sighting_id/index | 单个目击着陆页 |
| sighting edit | /sighting/:sighting_id/edit | 编辑单个目击的表单 |
使用Ember CLI的
ember generate
(简称
ember g
)工具创建这些路由,操作步骤如下:
1. 打开终端,导航到
tracker
目录。
2. 逐行运行以下命令:
ember g route index
ember g route sightings
ember g route sightings/index
ember g route sightings/new
ember g route sighting
ember g route sighting/index
ember g route sighting/edit
ember g route cryptids
ember g route cryptid
ember g route witnesses
ember g route witness
2.2 路由文件和模板分析
运行上述命令后,会在
routes/
和
templates/
目录下生成新文件。
打开
app/routes/index.js
,该模块导入Ember并导出一个
Ember.Route
:
import Ember from 'ember';
export default Ember.Route.extend({
});
.extend
方法创建
Ember.Route
的新子类,并接受一个JavaScript对象作为参数。使用ES6模块语法,可为每个路由创建单独的模块。Ember CLI会自动找到新创建的
Ember.Route
模块并导入到应用中。
打开
app/templates/index.hbs
,该模板用于
IndexRoute
,其中包含一行代码
{{outlet}}
,此助手允许模板在路由之间嵌套内容。在
{{outlet}}
上方添加一个HTML
<h1>
元素:
<h1>Index Route</h1>
{{outlet}}
启动应用:
1. 运行
ember server
启动应用。
2. 在项目开发过程中保持服务器运行。
3. 若需要与Ember CLI交互(如生成更多模块),可打开第二个终端窗口。
4. 在Chrome浏览器中导航到
http://localhost:4200
,应用应显示
app/templates/application.hbs
中的
NavBar
元素和
app/templates/index.hbs
中的
<h1>
元素。
2.3 路由注册
在
router.js
文件中注册路由,将URL与特定页面关联起来。每个路由可配置一些选项,甚至可创建嵌套路由。
以下是
router.js
中注册路由的代码:
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('sightings', function() {
this.route('new');
});
this.route('sighting', function() {
this.route('edit');
});
this.route('cryptids');
this.route('cryptid');
this.route('witnesses');
this.route('witness');
});
export default Router;
Router.map
方法接受一个回调函数,在回调函数中使用
this.route
方法注册路由。Ember会将这些嵌套回调转换为路由层次结构,顶层是
ApplicationRoute
。
当访问嵌套路由的URL时,Ember会使用父模板的HTML,并在父模板中查找
{{outlet}}
助手,将子模板的HTML放置在该位置。
2.4 嵌套路由
嵌套路由可将相关路由组合在一个基础URL下,有助于结构化视图中的数据。可将父路由视为代表名词,子路由视为代表动词或形容词。
例如:
this.route('sightings', function() {
this.route('new');
});
sightings
是代表目击事件的父路由(名词),
new
是代表创建目击事件操作的嵌套路由(动词)。
以下是编辑模板文件并导航到应用不同页面的操作步骤:
1. 打开
app/templates/sightings.hbs
,在现有
{{outlet}}
助手上方添加一个
<h1>
元素:
<h1>Sightings</h1>
{{outlet}}
-
编辑
app/templates/sightings/index.hbs,添加一个<h1>元素并删除{{outlet}}助手:
<h1>Index Route</h1>
-
保存文件,在浏览器中访问
http://localhost:4200/sightings/查看结果。 -
编辑
app/templates/sightings/new.hbs,删除{{outlet}}并添加一个<h1>元素:
<h1>New Route</h1>
-
更改浏览器URL为
http://localhost:4200/sightings/new查看结果。
2.5 Ember Inspector查看路由
使用Ember Inspector可轻松查看应用的所有路由。点击Ember Inspector中的“Routes”菜单项,可看到众多路由,其中包括许多以
loading
和
error
结尾的自动生成路由,这些路由用于处理路由加载数据的生命周期状态。
2.6 分配模型
为每个路由分配数据,可使用路由的
model
回调函数。每个
Ember.Route
都有一个
model
方法,用于将模型(即支持模板的数据)分配给控制器,该方法返回一个Promise。
在
app/routes/sightings.js
的
model
回调中添加一些模拟数据:
import Ember from 'ember';
export default Ember.Route.extend({
model(){
return [
{
id: 1,
location: 'Asilomar',
sightedAt: new Date('2016-03-07')
},
{
id: 2,
location: 'Asilomar',
sightedAt: new Date('2016-03-07')
},
{
id: 3,
location: 'Asilomar',
sightedAt: new Date('2016-03-07')
},
{
id: 4,
location: 'Asilomar',
sightedAt: new Date('2016-03-07')
},
{
id: 5,
location: 'Asilomar',
sightedAt: new Date('2016-03-07')
},
{
id: 6,
location: 'Asilomar',
sightedAt: new Date('2016-03-07')
}
];
}
});
model
钩子的语法如下:
model() {
// 代码逻辑
}
这是以下代码的ES6简写形式:
model: function() {
// 代码逻辑
}
编辑
app/templates/sightings/index.hbs
,显示模型数据:
<h1>Index Route</h1>
<div class="panel panel-default">
<ul class="list-group">
{{#each model as |sighting|}}
<li class="list-group-item">
{{sighting.location}} - {{sighting.sightedAt}}
</li>
{{/each}}
</ul>
</div>
在浏览器中访问
http://localhost:4200/sightings
,应用应显示目击列表。
2.7 beforeModel钩子
beforeModel
函数可用于在检索数据之前检查应用状态,也可用于重定向无法访问特定页面的用户,如进行用户身份验证检查。
在
app/routes/index.js
中添加
beforeModel
回调:
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel(){
this.transitionTo('sightings');
}
});
当访问
http://localhost:4200/
时,URL会自动重定向到
http://localhost:4200/sightings
,并显示
app/templates/sightings/index.hbs
中的目击列表。
2.8 setupController和afterModel钩子
-
setupController钩子用于在控制器上设置属性,可使用this._super运行设置控制器模型属性的默认行为,同时设置其他活动控制器属性:
setupController(controller, model) {
this._super(controller, model);
// this.controllerFor('[other controller]').set("[property name]", [value]);
}
-
afterModel钩子在model钩子(Promise)解析后运行。在某些特殊情况下,由于Promise已解析,model钩子可能不会被调用,此时afterModel会在setupController之前被调用,可用于在将模型数据传递给控制器之前测试其完整性。
综上所述,通过上述步骤,应用已具备一些基本路由,包括着陆页、目击列表和添加新目击的路由。创建了路由模板,并向
sightings
路由添加了模型数据,还将
index
路由重定向到
sightings
索引页,为后续开发奠定了良好基础。
3. 路由生命周期总结
路由生命周期是 Ember 应用中非常重要的一部分,它涉及到多个钩子函数的调用顺序,下面通过一个流程图来展示整个路由周期:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A([开始]):::startend --> B(触发 URL 变化):::process
B --> C(beforeModel):::process
C --> D{是否满足条件}:::process
D -->|是| E(model):::process
D -->|否| K(重定向或其他处理):::process
E --> F(afterModel):::process
F --> G(setupController):::process
G --> H(渲染模板):::process
H --> I([结束]):::startend
K --> I
从这个流程图可以清晰地看到,当 URL 发生变化时,路由对象会依次调用
beforeModel
、
model
、
afterModel
和
setupController
等函数。其中,
beforeModel
可用于检查应用状态和重定向用户;
model
用于获取渲染模板所需的数据;
afterModel
可在数据获取后进行一些额外处理;
setupController
则将模型数据设置到控制器上。
4. 路由相关代码优化建议
4.1 代码复用
在 Ember 应用中,许多路由可能会有一些共同的逻辑。例如,多个路由可能都需要进行用户身份验证检查,这时可以将这些共同的逻辑提取到一个基类中。
以下是一个示例:
// app/routes/base-route.js
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel() {
// 检查用户身份验证
if (!this.get('session.isAuthenticated')) {
this.transitionTo('login');
}
this._super(...arguments);
}
});
// app/routes/some-route.js
import BaseRoute from './base-route';
export default BaseRoute.extend({
// 其他特定逻辑
});
通过这种方式,多个路由可以继承
BaseRoute
,避免代码重复。
4.2 异步数据处理
在获取数据时,由于
model
方法返回的是一个 Promise,可能会遇到异步操作的问题。为了提高用户体验,可以在页面加载时显示加载状态。
以下是一个示例:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
id: 1,
name: 'Item 1'
},
{
id: 2,
name: 'Item 2'
}
]);
}, 2000);
});
},
setupController(controller, model) {
this._super(controller, model);
controller.set('isLoading', false);
},
activate() {
this.controllerFor(this.routeName).set('isLoading', true);
}
});
在模板中可以根据
isLoading
状态显示加载提示:
{{#if isLoading}}
<p>Loading...</p>
{{else}}
<!-- 显示数据 -->
<ul>
{{#each model as |item|}}
<li>{{item.name}}</li>
{{/each}}
</ul>
{{/if}}
5. 常见问题及解决方案
5.1 路由跳转失败
-
问题描述
:在调用
transitionTo或replaceWith方法时,路由跳转失败。 - 可能原因 :
- 路由名称拼写错误。
- 路由未正确注册。
- 路由参数传递错误。
- 解决方案 :
- 检查路由名称是否正确,可通过 Ember Inspector 查看路由列表。
-
确保在
router.js中正确注册了路由。 - 检查传递的路由参数是否符合路由定义。
5.2 模型数据未正确加载
- 问题描述 :在模板中无法显示模型数据。
- 可能原因 :
-
model方法返回的数据格式不正确。 - 数据获取过程中出现错误。
- 控制器未正确接收模型数据。
- 解决方案 :
-
检查
model方法返回的数据格式是否符合模板的使用要求。 -
在
model方法中添加错误处理逻辑,例如:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return new Promise((resolve, reject) => {
// 模拟数据获取
setTimeout(() => {
const error = false; // 模拟错误情况
if (error) {
reject(new Error('Data fetch failed'));
} else {
resolve([
{
id: 1,
name: 'Item 1'
}
]);
}
}, 1000);
}).catch((error) => {
console.error(error);
return [];
});
}
});
-
确保在
setupController方法中正确设置了模型数据。
6. 总结与展望
通过前面的介绍,我们了解了 Ember 应用中路由和模型的相关知识,包括路由的创建、嵌套、生命周期钩子的使用,以及如何为路由分配模型数据等。这些知识为构建复杂的 Ember 应用奠定了基础。
在未来的开发中,可以进一步探索以下方面:
-
更复杂的路由配置
:例如动态路由、路由守卫等,以实现更灵活的页面导航和权限控制。
-
与后端服务的集成
:使用 Ember Data 等工具与后端 API 进行交互,实现数据的持久化和实时更新。
-
性能优化
:通过缓存、懒加载等技术提高应用的响应速度和性能。
总之,Ember 提供了强大的路由和模型机制,合理运用这些技术可以开发出高效、稳定的 Web 应用。
超级会员免费看

10

被折叠的 条评论
为什么被折叠?



