Ember开发:路由动作、组件创建与应用实践
1. 路由动作(Route Actions)
在开发中,动作(Actions)并非仅适用于控制器(Controllers),路由(Routes)也能为模板声明动作并覆盖生命周期动作。当调用动作时,它会从模板冒泡到控制器,再到路由,最后到父路由。
1.1 路由充当控制器
在某些情况下,当不需要控制器定义时,路由可以充当控制器。虽然这看似与分离应用关注点的理念相悖,但实际上,开发将控制器的工作拆分为路由信息和控制器逻辑两部分。有时路由文件包含更多逻辑,有时控制器包含更多逻辑。文件分离有助于在另一个文件有大量动作或装饰器时,将代码拆分成易于理解的小块。
1.2 迁移动作示例
为了说明这一过程,我们将把创建(create)和取消(cancel)动作从
app/controllers/sightings/new.js
迁移到
app/routes/sightings/new.js
。
1.2.1 在路由文件中添加动作
在
app/routes/sightings/new.js
中添加以下代码:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
...
},
sighting: Ember.computed.alias('controller.model.sighting'),
actions: {
willTransition() {
var sighting = this.get('controller.model.sighting');
if(sighting.get('hasDirtyAttributes')) {
sighting.deleteRecord();
}
},
create() {
var self = this;
this.get('sighting').save().then(function(data) {
self.transitionTo('sightings');
});
},
cancel() {
this.get('sighting').deleteRecord();
this.transitionToRoute('sightings');
}
}
});
这里为路由中的
sighting
对象创建了一个
Ember.computed.alias
。从路由访问控制器的
model.sighting
对象时,主要区别在于该对象的位置。“new” 路由上的模型与 “new” 控制器上的模型不同。为了访问
sighting
对象,使用
get('controller.model.sighting')
。创建该对象的别名可以避免每次都输入这么长的代码。
1.2.2 从控制器文件中删除动作
在
app/controllers/sightings/new.js
中删除相应动作:
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
create() {
var self = this;
this.get('model.sighting').save().then(function() {
self.transitionToRoute('sightings');
});
},
cancel() {
this.get('model.sighting').deleteRecord();
this.transitionToRoute('sightings');
}
}
});
完成上述操作后,确保应用重新加载(或重启 Ember 服务器)。导航到
http://localhost:4200/sightings/new
并创建一个新的目击记录,以确保仍能运行路由动作。此时,
app/controllers/sightings/new.js
文件已不再需要,可以删除。虽然 Ember 在应用运行时仍会创建控制器对象,但空文件会使应用目录变得杂乱。
1.3 路由动作的作用
通过上述操作,我们可以看到路由动作允许我们通过简单的回调来更改应用的模型数据。控制器属性允许我们在向模型数据添加关系之前设置特定页面的属性。此外,我们还完成了从编辑路由更新和删除记录的基本 CRUD 操作。Ember 允许在需要为路由设置特定细节时,选择是否创建控制器,从而实现快速开发。
1.4 挑战任务
-
青铜挑战:目击详情页面
:创建一个目击详情页面,使用
app/templates/sighting/index.hbs和app/routes/sighting.js来显示神秘生物的图像、位置和目击者列表,并添加一个编辑按钮。 -
白银挑战:目击日期
:在创建和编辑目击记录时,向控制器添加
sightingDate属性,并添加一个绑定到该属性的输入框。使用基本文本输入框或input[type="date"]来捕获目击日期。使用moment将日期转换为 ISO8601 字符串,并将该日期字符串设置为目击属性sightingAt。 -
黄金挑战:添加和删除目击者
:在创建新的目击记录时,通过
select的onchange动作构建一个目击者列表。创建一个新的属性来存储目击者列表。在创建时,将对象添加到目击属性witnesses中。在页面上,将所选目击者的集合渲染为带有删除按钮的列表。使用select元素添加到列表中,同时从select元素中移除选项。需要两个动作:addWitness和removeWitness。
2. 组件(Components)
组件是 Ember 中包含控制器和视图属性的对象。组件的概念是创建具有自己上下文或作用域的可重用 DOM 元素。组件接受属性来定制其模板内渲染的内容,并且可以将动作分配给父控制器或父路由的属性。
2.1 迭代器项作为组件
在开发中,
{{#each}}
迭代器中渲染的元素通常可以成为组件。例如,有一个基于迭代器对象具有特定
className
的
<div>
容器元素,包含标题、图像路径、带有动作的按钮和基于对象属性状态的样式。为了使这些元素易于重用,可以将所有 DOM 代码包装在组件模板中,并创建一个组件 JavaScript 文件来处理控制器通常处理的装饰器和动作。
2.1.1 创建组件
以目击列表为例,我们将创建一个组件来表示目击列表项。在终端中使用以下命令生成组件:
ember g component listing-item
这将创建三个文件:
app/components/listing-item.js
、
app/templates/components/listing-item.hbs
和测试文件
tests/integration/components/listing-item-test.js
。
2.1.2 迁移代码
打开
app/templates/sightings/index.hbs
,找到以下代码块:
<div class="row">
{{#each model as |sighting|}}
<div class="col-xs-12 col-sm-3 text-center">
<div class="media well">
<img class="media-object thumbnail" src="{{if sighting.cryptid.profileImg
sighting.cryptid.profileImg 'assets/images/cryptids/blank_th.png'}}"
alt="{{sighting.cryptid.name}}" width="100%" height="100%">
<div class="caption">
<h3>{{sighting.cryptid.name}}</h3>
{{#if sighting.location}}
<h3>{{sighting.location}}</h3>
<p>{{moment-from sighting.sightedAt}}</p>
{{else}}
<h3 class="text-danger">Bogus Sighting</h3>
{{/if}}
</div>
{{#link-to 'sighting.edit' sighting.id tagName="button"
class="btn btn-success btn-block"}}
Edit
{{/link-to}}
</div>
</div>
{{/each}}
</div>
其中,带有
col-xs-12
类的
<div>
容器是特定页面的视觉容器,用于指定内容大小,不能添加到组件中。而
<div class="media well">
容器及其内容可以迁移到组件中。
2.1.3 编辑组件模板
打开
app/templates/components/listing-item.hbs
,添加以下代码:
<img class="media-object thumbnail" src="{{imagePath}}" alt="{{name}}"
width="100%" height="100%">
<div class="caption">
<h3>{{name}}</h3>
{{yield}}
</div>
当将该组件添加到路由模板时,它将渲染以下内容:
<div>
<img class="media-object thumbnail" src="[cryptid's imagePath string]"
alt="[cryptid's name string]" width="100%" height="100%">
<div class="caption">
<h3>[cryptid's name string]</h3>
{{yield}}
</div>
</div>
模板中的动态部分
{{name}}
和
{{imagePath}}
将作为属性提供给组件。
{{yield}}
用于将子元素传递给组件,以便在 DOM 节点结构的特定位置渲染。
2.1.4 替换现有代码
在
app/templates/sightings/index.hbs
中用组件替换现有代码:
<div class="row">
{{#each model as |sighting|}}
<div class="col-xs-12 col-sm-3 text-center">
<div class="media well">
<img class="media-object thumbnail" src="{{if sighting.cryptid.profileImg
sighting.cryptid.profileImg 'assets/images/cryptids/blank_th.png'}}"
alt="{{sighting.cryptid.name}}" width="100%" height="100%">
<div class="caption">
<h3>{{sighting.cryptid.name}}</h3>
{{#if sighting.location}}
<h3>{{sighting.location}}</h3>
<p>{{moment-from sighting.sightedAt}}</p>
{{else}}
<h3 class="text-danger">Bogus Sighting</h3>
{{/if}}
</div>
{{#link-to 'sighting.edit' sighting.id tagName="button"
class="btn btn-success btn-block"}}
Edit
{{/link-to}}
</div>
{{#listing-item imagePath=sighting.cryptid.profileImg
name=sighting.cryptid.name}}
{{/listing-item}}
</div>
{{/each}}
</div>
虽然这一过程中移除了一些功能,但后续会进行修复。
2.1.5 添加容器样式
打开
app/components/listing-item.js
,添加
classNames
属性:
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ["media", "well"]
});
此时,渲染的组件如下:
<div class="media well">
<img class="media-object thumbnail" src="[imagePath string]" alt="[name string]"
width="100%" height="100%">
<div class="caption">
<h3>[name string]</h3>
{{yield}}
</div>
</div>
2.1.6 添加上下文内容
在
app/templates/sightings/index.hbs
中,向组件的
{{yield}}
部分添加上下文内容:
{{#listing-item imagePath=sighting.cryptid.profileImg
name=sighting.cryptid.name}}
{{#if sighting.location}}
<h3>{{sighting.location}}</h3>
<p>{{moment-from sighting.sightedAt}}</p>
{{else}}
<h3 class="text-danger">Bogus Sighting</h3>
{{/if}}
{{#link-to 'sighting.edit' sighting.id tagName="button"
class="btn btn-success btn-block"}}
Edit
{{/link-to}}
{{/listing-item}}
这将恢复目击位置、时间和编辑按钮的显示。
2.2 组件实现代码复用(DRY)
DRY 是一种编程原则,即 “不要重复自己”。在开发中,目击和神秘生物的列表都使用了带有图像和标题的
class="media well"
元素,因此可以使用组件来实现代码复用。
2.2.1 添加组件到神秘生物列表
在
app/templates/cryptids.hbs
中,用新的
{{#listing-item}}
组件替换
<div class="media well">
元素及其子元素:
<div class="row">
{{#each model as |cryptid|}}
<div class="col-xs-12 col-sm-3 text-center">
<div class="media well">
{{#link-to 'cryptid' cryptid.id}}
<img class="media-object thumbnail"
src="{{if cryptid.profileImg cryptid.profileImg
'assets/images/cryptids/blank_th.png'}}"
alt="{{cryptid.name}}" width="100%" height="100%">
{{/link-to}}
<div class="caption">
<h3>{{cryptid.name}}</h3>
</div>
</div>
{{#link-to 'cryptid' cryptid.id}}
{{listing-item imagePath=cryptid.profileImg name=cryptid.name}}
{{/link-to}}
</div>
{{else}}
<div class="jumbotron">
<h1>No Creatures</h1>
</div>
{{/each}}
</div>
这里注意到,在两个模板中引用组件的方式有所不同。目击迭代器使用
{{yield}}
在
<div class="caption">
内添加元素,而神秘生物模板没有使用。当只需要使用组件模板文件中的代码时,可以使用内联组件(如
{{listing-item}}
),这种语法没有关闭的 HTMLBars 元素(
{{/listing-item}}
)。通过将组件包装在
{{#link-to}}
中,可以使整个元素链接到神秘生物详情页面,展示了组件在不同路由模板上下文中渲染相似内容的灵活性。
2.3 数据向下,动作向上(Data Down, Actions Up)
组件的一个原则是 “数据(或状态)向下,动作向上”。与控制器不同,组件不应该更改应用的状态,而应该通过动作将更改传递上去。组件的状态应该从父模板传递下来。
2.3.1 创建全局消息组件
我们将创建一个新的组件和动作,并将组件添加到
app/templates/application.hbs
文件中,以便在创建新的目击记录时显示全局消息。
2.3.2 生成组件
在终端中生成新组件:
ember g component flash-alert
2.3.3 编辑组件模板
打开并编辑
app/templates/components/flash-alert.hbs
:
{{yield}}
<strong>{{typeTitle}}!</strong> {{message}}
2.3.4 编辑组件文件
编辑
app/components/flash-alert.js
并添加
classNames
:
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ["alert"]
});
此时,组件将渲染以下元素:
<div class="alert">
<strong>{{typeTitle}}!</strong> {{message}}
</div>
2.3.5 类名绑定(Class Name Bindings)
为了设置正确的警报类型,我们需要使用计算属性和类名绑定来添加 Bootstrap 的状态变体样式(如
alert-success
、
alert-info
、
alert-warning
和
alert-danger
)。
2.3.5.1 添加计算属性
在
app/components/flash-alert.js
中添加计算属性:
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ["alert"],
typeClass: Ember.computed('alertType', function() {
return "alert-" + this.get('alertType');
})
});
这里添加了一个
typeClass
计算属性,用于为组件的
<div>
元素添加类名。该计算属性期望一个名为
alertType
的属性,并返回一个以
alert-
为前缀的字符串。
2.3.5.2 添加类名绑定
在
app/components/flash-alert.js
中添加类名绑定:
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ["alert"],
classNameBindings: ['typeClass'],
typeClass: Ember.computed('alertType', function() {
return "alert-" + this.get('alertType');
})
});
通过这种方式,当设置
alertType
属性时,组件的样式会相应改变。
2.3.5.3 添加显示警报类型的计算属性
在
app/components/flash-alert.js
中添加
typeTitle
计算属性:
import Ember from 'ember';
export default Ember.Component.extend({
...
typeClass: Ember.computed('alertType', function() {
return "alert-" + this.get('alertType');
}),
typeTitle: Ember.computed('alertType', function() {
return Ember.String.capitalize(this.get('alertType'));
})
});
这样,消息中绑定的警报类型将以大写字符串显示为
<strong>
元素。
2.3.6 添加组件到页面
在
app/templates/application.hbs
中添加组件:
<header>
...
</header>
<div class="container">
{{flash-alert}}
{{outlet}}
</div>
这个组件是内联组件,不需要在
{{yield}}
中渲染元素,组件模板也不需要
{{yield}}
。
2.4 总结
通过以上内容,我们了解了如何使用路由动作来更改应用的模型数据,以及如何创建和使用组件来实现代码复用和灵活的界面渲染。组件的 “数据向下,动作向上” 原则有助于保持应用状态的清晰和可维护性。同时,通过类名绑定和计算属性,我们可以根据组件的状态动态设置样式。这些技术在开发中可以提高代码的可重用性和可维护性,使开发更加高效。
2.5 流程图
graph TD;
A[开始] --> B[创建组件];
B --> C[编辑组件模板];
C --> D[编辑组件文件];
D --> E[添加类名绑定和计算属性];
E --> F[添加组件到页面];
F --> G[完成];
2.6 表格
| 挑战等级 | 挑战内容 |
|---|---|
| 青铜挑战 | 创建目击详情页面,显示神秘生物图像、位置和目击者列表,添加编辑按钮 |
| 白银挑战 |
在创建和编辑目击记录时,添加
sightingDate
属性和绑定输入框,转换日期并设置
sightingAt
|
| 黄金挑战 |
创建目击者列表,添加和删除目击者,实现
addWitness
和
removeWitness
动作
|
3. 组件应用总结与拓展
3.1 组件的优势总结
组件在 Ember 开发中具有诸多优势,以下是一个简要的总结表格:
| 优势 | 说明 |
| ---- | ---- |
| 代码复用 | 如
listing-item
组件,可在不同列表中复用,减少代码重复,提高开发效率。 |
| 可维护性 | 组件将相关的 DOM 元素、样式和逻辑封装在一起,便于修改和扩展。 |
| 灵活性 | 组件可以根据不同的上下文进行定制,如通过
{{yield}}
传递子元素,适应不同的需求。 |
| 状态管理 | 遵循 “数据向下,动作向上” 原则,使应用状态的管理更加清晰和可预测。 |
3.2 组件开发流程回顾
为了更好地理解组件的开发过程,我们可以通过以下 mermaid 流程图进行回顾:
graph LR;
A[确定可复用元素] --> B[生成组件];
B --> C[编辑组件模板];
C --> D[编辑组件文件];
D --> E[添加必要属性和动作];
E --> F[在模板中使用组件];
F --> G[测试和优化];
3.3 组件开发的注意事项
在开发组件时,还需要注意以下几点:
1.
属性传递
:确保正确传递属性给组件,避免出现未定义或错误的值。
2.
动作处理
:合理设计动作,确保动作能够正确响应并传递到父组件或路由。
3.
样式管理
:使用类名绑定和计算属性来动态管理组件的样式,避免硬编码。
4.
测试
:对组件进行充分的测试,确保其在不同场景下都能正常工作。
3.4 组件的进一步拓展
组件的应用可以进一步拓展,例如:
1.
嵌套组件
:可以创建嵌套组件,将复杂的界面拆分成多个小的组件,提高代码的可维护性。
2.
动态组件
:根据不同的条件动态加载不同的组件,实现更灵活的界面展示。
3.
组件库
:将常用的组件整理成组件库,方便在不同项目中复用。
4. 综合实践与总结
4.1 综合实践案例
假设我们要开发一个更复杂的应用,包含多个列表和消息提示功能。我们可以综合运用前面所学的知识,创建不同的组件来实现。
4.1.1 创建更多组件
除了
listing-item
和
flash-alert
组件,我们还可以创建其他组件,如
pagination
组件用于分页,
search-box
组件用于搜索。
4.1.2 组件的组合使用
在不同的页面中,将这些组件组合使用,实现不同的功能。例如,在列表页面中使用
listing-item
和
pagination
组件,在搜索页面中使用
search-box
和
listing-item
组件。
4.1.3 状态管理
通过 “数据向下,动作向上” 原则,管理组件之间的状态传递。例如,在
search-box
组件中输入搜索关键词,将关键词传递给父组件,父组件再将关键词传递给
listing-item
组件进行过滤。
4.2 总结
通过以上的学习和实践,我们掌握了 Ember 开发中路由动作和组件的使用方法。路由动作可以帮助我们处理应用的业务逻辑,而组件则可以提高代码的复用性和可维护性。在实际开发中,我们可以根据具体需求灵活运用这些技术,开发出高效、可维护的应用。
4.3 未来展望
随着技术的不断发展,Ember 框架也在不断更新和完善。未来,我们可以关注以下方面的发展:
1.
性能优化
:学习更多的性能优化技巧,提高应用的响应速度和用户体验。
2.
新特性学习
:关注 Ember 框架的新特性,如 Web Components 的集成等。
3.
生态系统
:了解 Ember 生态系统中的其他工具和库,如 Ember CLI Addons,进一步提高开发效率。
总之,Ember 开发为我们提供了丰富的工具和技术,通过不断学习和实践,我们可以开发出更加优秀的应用。
超级会员免费看
13

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



