作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
灰烬.js 是构建复杂客户端应用程序的全面框架吗. 它的原则之一是“约定优于配置”,,并坚信大多数web应用程序的开发有很大一部分是共同的, 因此,这是解决大多数日常挑战的最佳方法. 然而, 找到正确的抽象, 并且涵盖了所有的情况, 需要整个社区的时间和投入. 随着推理的进行, 最好花点时间找出解决核心问题的正确方法, 然后将其放入框架中, 而不是举手投降,让每个人在需要找到解决方案的时候自生自灭.
灰烬.Js是不断发展的 开发更容易. 但是,与任何高级框架一样,灰烬开发者仍然可能陷入陷阱. 通过下面的帖子,我希望提供一个地图来逃避这些. 让我们开始吧!
让我们假设我们的应用程序中有以下路由:
路由器.地图(函数(){
这.路线 ('乐队', {path: '乐队/:id'}, function() {
这.路线(“歌曲”);
});
});
的 乐队
路由有一个动态段, id
. 当用URL加载应用程序时 /带/ 24
, 24
传递给 模型
对应路线的钩子, 乐队
. 模型钩子的作用是反序列化段,以创建一个对象(或一个对象数组),然后可以在模板中使用:
/ / app /线路/乐队.js
导出默认灰烬.路线.扩展({
模型:function(params) {
返回这.商店.找到(“乐队”,参数个数.id); // params.Id是24
}
});
到目前为止一切顺利. 然而, 除了从浏览器导航栏加载应用程序之外,还有其他输入路由的方法. 其中一个是用 链接到
来自模板的助手. 下面的代码片段遍历一个频带列表,并创建指向它们各自的链接 乐队
路线:
{{#每个波段作为|波段|}}
{{链接带.名称“乐队”乐队}}
{{/每个}}
链接到的最后一个参数, 乐队
是一个对象,它填充了路由的动态段,因此它的 id
成为该路由的id段. 许多人陷入的陷阱是,在这种情况下没有调用模型钩子, 因为模型是已知的,并且已经传入. 这是有意义的,它可能会节省对服务器的请求,但不可否认,这并不直观. 一种巧妙的方法是传入的不是物体本身,而是它的id:
{{#每个波段作为|波段|}}
{{链接带.名字“乐队”乐队.id}}
{{/每个}}
路由组件将很快出现在灰烬中, 可能是版本2.1 or 2.2. 当他们着陆时, 模型钩子总是会被调用, 无论如何转换到具有动态段的路由. 请阅读相应的RFC 在这里.
烬中的路线.Js在控制器上设置属性,作为相应模板的上下文. 这些控制器是单例的,因此即使控制器不再活动,在它们上面定义的任何状态也会持续存在.
这是很容易被忽视的 我也是偶然发现的. 就我而言,我有一个包含乐队和歌曲的音乐目录应用程序. 的 songCreationStarted
旗帜上的旗帜 歌曲
控制器指示用户已开始为特定乐队创作歌曲. 问题是,如果用户切换到另一个波段,值 songCreationStarted
这首半成品的歌似乎是给另一支乐队的,这让人很困惑.
解决方案是手动重置我们不想逗留的控制器属性. 一个可能的地方是 集upController
类之后的所有转换都会调用该钩子 afterModel
钩子(顾名思义,在钩子之后) 模型
钩):
/ / app /线路/乐队.js
导出默认灰烬.路线.扩展({
集upController:函数(控制器,模型){
这._super(控制器、模型);
控制器.设置(“songCreationStarted”,假);
}
});
再一次,黎明 routeable组件 能解决这个问题,彻底终结控制器吗. 可路由组件的优点之一是,它们具有更一致的生命周期,并且在从它们的路由转换时总是被拆除. 当他们到达时,上述问题就会消失.
集upController
灰烬中的路由有一些生命周期钩子来定义特定于应用程序的行为. 我们已经看到 模型
哪个用于获取相应模板和的数据 集upController
,用于设置控制器、模板的上下文.
后者, 集upController
,有一个合理的默认值,即从 模型
像钩子一样 模型
控制器属性:
/ / ember-routing / lib /系统/路线.js
集upController(控制器, 上下文, transition) {
如果控制器 && (上下文 !==未定义)){
Set (控制器, '模型', 上下文);
}
}
(上下文
使用的名称是 ember-routing
我称之为 模型
上图)
的 集upController
钩子可以出于多种目的被重写, 如重置控制器的状态(如常见错误No . 1). 2以上). 但是,如果忘记调用我上面在灰烬中复制的父实现.路由,一个人可以在一个长头痛的会话,因为控制器将没有它的 模型
属性集. 所以一定要打电话 这._super(控制器,模型)
:
导出默认灰烬.路线.扩展({
集upController:函数(控制器,模型){
这._super(控制器、模型);
//将自定义设置放在这里
}
});
如前所述,控制器以及与它们一起的 集upController
钩子很快就会消失,所以这个陷阱将不再是一个威胁. 然而, 这里有一个更重要的教训, 哪一个是要注意祖先的实现. 的 初始化
函数,定义于 灰烬.Object
烬中所有物体的母亲,是另一个你必须注意的例子.
这.梅尔
非父路由灰烬路由器在处理URL时解析每个路由段的模型. 让我们假设我们的应用程序中有以下路由:
路由器.地图({
这.路线 ('乐队', function() {
这.路由('乐队', {path: ':id'},函数(){
这.路线(“歌曲”);
});
});
});
给定URL为 /带/ 24 /歌曲
, 模型
钩的 乐队
, 乐队.乐队
然后 乐队.乐队.歌曲
按这个顺序. 路由API有一个特别方便的方法, 梅尔
, 可以在子路由中使用,从父路由中获取模型, 因为那个模型到那时肯定已经解决了.
方法中获取乐队对象的有效方法 乐队.乐队
路线:
/ /应用程序/线路/带/乐队.js
导出默认灰烬.路线.扩展({
模型:function(params) {
Var波段=这个.梅尔(“乐队”);
返回乐队.filterBy(“id”,参数个数.id);
}
});
然而,一个常见的错误是在梅尔中使用路由名 不 路由的父节点. 如果上面例子中的路由稍微改变一下:
路由器.地图({
这.路线(“乐队”);
这.路线 ('乐队', {path: '乐队/:id'}, function() {
这.路线(“歌曲”);
});
});
我们获取URL中指定的频带的方法将中断,因为 乐队
路线不再是父节点,因此它的模型没有被解析.
/ /应用程序/线路/带/乐队.js
导出默认灰烬.路线.扩展({
模型:function(params) {
Var波段=这个.梅尔(“乐队”); // `乐队` is undefined
返回乐队.filterBy(“id”,参数个数.id); // => error!
}
});
解决办法是使用 梅尔
只用于父路由,并使用其他方式检索必要的数据时 梅尔
不能使用,比如取回
从商店.
/ /应用程序/线路/带/乐队.js
导出默认灰烬.路线.扩展({
模型:function(params) {
返回这.商店.找到(“乐队”,参数个数.id);
}
});
嵌套组件一直是灰烬中最难理解的部分之一. 随着 块参数在灰烬 1.10, 这种复杂性在很大程度上得到了缓解, 但在很多情况下, 看一眼哪个组件是一个动作仍然很棘手, 从子组件触发, 将被触发.
假设有a 乐队表
组件 乐队表-项s
在里面,我们可以把每个乐队标记为最喜欢的乐队.
/ /应用程序/模板/组件/乐队表.哈佛商学院
{{#每个波段作为|波段|}}
{{乐队表-项 乐队=乐队 faveAction="集AsFavorite"}}
{{/每个}}
当用户单击按钮时应该调用的操作名称被传递到 乐队表-项
组件,并成为其值 faveAction
财产.
的模板和组件定义 乐队表-项
:
/ /应用程序/模板/组件/ 乐队表-项.哈佛商学院
{{乐队.name}}
/ / app /组件/ 乐队表-项.js
导出默认灰烬.组件.扩展({
乐队:空,
faveAction:”,
行动:{
faveBand: {
这.sendAction(“faveAction”.('带'));
}
}
});
当用户点击“喜欢这个”按钮时 faveBand
动作被触发,从而触发组件的 faveAction
这是在(集AsFavorite
(在上述情况下), 在其父组件上, 乐队表
.
这让很多人感到困惑,因为他们希望动作被触发的方式和路由驱动模板中的动作一样, 在控制器上(然后在活动路由上冒泡). What makes 这 worse is that no error message is logged; the parent component just swallows the error.
一般规则是在当前上下文上触发操作. 对于非组件模板, 这个上下文就是当前控制器, 而对于组件模板来说, 它是父组件(如果有的话), 如果组件没有嵌套,则再次使用当前控制器.
在上面的例子中 乐队表
组件必须重新启动从 乐队表-项
以便将其起泡到控制器或路由.
/ / app /组件/乐队表.js
导出默认灰烬.组件.扩展({
乐队:[],
favoriteAction:“集FavoriteBand”,
行动:{
集AsFavorite:函数(乐队) {
这.sendAction (favoriteAction,乐队);
}
}
});
如果 乐队表
在 乐队
模板,然后 集FavoriteBand
操作必须在 乐队
控制器或 乐队
路由(或它的父路由之一).
您可以想象,如果有更多的嵌套级别(例如, 通过 fav-button
内部组件 乐队表-项
). 你必须从里面钻一个洞,穿过几层才能把你的信息发出来, 在每个级别定义有意义的名称(集AsFavorite
, favoriteAction
, faveAction
等.)
这就简化了 “改进的行动RFC”,它已经在主分支上可用,并且可能会包含在1中.13.
然后将上述示例简化为:
/ /应用程序/模板/组件/乐队表.哈佛商学院
{{#每个波段作为|波段|}}
{{乐队表-项 乐队=乐队 集FavBand=(action "集FavoriteBand")}}
{{/每个}}
/ /应用程序/模板/组件/ 乐队表-项.哈佛商学院
{{乐队.name}}
烬的计算属性依赖于其他属性, 这个依赖需要由开发人员显式地定义. 假设我们有一个 isAdmin
属性,当且仅当其中一个角色为 管理
. 可以这样写:
isAdmin:函数(){
返回这.get(角色).包含('管理');
}.属性(角色)
根据上述定义,的值 isAdmin
只有当 角色
数组对象本身发生变化,但如果向现有数组添加或删除项,则不会发生变化. 有一个特殊的语法来定义添加和删除也应该触发重新计算:
isAdmin:函数(){
返回这.get(角色).包含('管理');
}.属性(的角色.[]')
让我们从常见错误1中扩展(现在已经修复)的例子. 6、在我们的应用中创建一个用户类.
var 用户 = 灰烬.Object.扩展({
初始化Roles: function() {
Var 角色 = 这.(角色);
if (!角色){
这.设置(“角色”,[]);
}
}.(“初始化”),
isAdmin:函数(){
返回这.get(角色).包含('管理');
}.属性(的角色.[]')
});
当我们加入 管理
这样的角色 用户
,我们会有一个惊喜:
var 用户 = 用户.create ();
用户.get('isAdmin'); // => false
用户.get(角色).推动(管理);
用户.get('isAdmin'); // => false ?
问题是,如果使用stock Javascript方法,观察者不会触发(因此计算的属性不会得到更新). 这可能会改变,如果全球采用 Object.观察
在浏览器中得到了改进,但在此之前,我们必须使用灰烬提供的一组方法. 在目前的情况下, 推Object
观察者友好的等效物是 推
:
用户.get(角色).推Object('管理');
用户.get('isAdmin'); // => true, finally!
假设我们有一个 星级
组件,该组件显示项目的评级并允许设置项目的评级. 评分可以是一首歌、一本书或一个足球运动员的运球技术.
你可以在你的模板中这样使用它:
{{#每首歌作为|song|}}
{{星级 项=song 评级=song.评级}}
{{/每个}}
让我们进一步假设组件显示恒星, 一颗星代表一个点, 然后是空荡荡的星星, 直到最高评级. 当一个星号被点击时,一个 集
动作在控制器上被触发, 它应该被解释为用户想要更新评级. 我们可以编写以下代码来实现这一点:
/ / app /组件/星级.js
导出默认灰烬.组件.扩展({
项目:空,
等级:0,
(...)
行动:{
集: function(newRating) {
Var 项 = 这.(“项目”);
项.设置(“评级”,newRating);
返回项目.save ();
}
}
});
这样就可以完成工作了,但是有几个问题. 首先,它假设传入的项有 评级
财产, 所以我们不能使用这个组件来管理梅西的运球技术(这个属性可能会被调用) 分数
).
其次,它改变了组件中项目的评级. 这就导致很难看出为什么某个属性会发生变化. 假设我们在同一个模板中有另一个组件,其中也使用了该评级, 例如, 用于计算足球运动员的平均分.
缓解这种情况复杂性的口号是“数据下降,行动上升”(DDAU)。. 数据应该向下传递(从路由到控制器再到组件), 而组件应该使用操作将这些数据的更改通知其上下文. 那么DDAU在这里应该如何应用呢?
让我们添加一个用于更新评级的动作名称:
{{#每首歌作为|song|}}
{{星级 项=song 评级=song.评级集Action = " updateRating "}}
{{/每个}}
然后用那个名字发送动作:
/ / app /组件/星级.js
导出默认灰烬.组件.扩展({
项目:空,
等级:0,
(...)
行动:{
集: function(newRating) {
Var 项 = 这.(“项目”);
这.sendAction (集Action, {
项目:.(“项目”),
评级:newRating
});
}
}
});
最后, 该操作在上游处理, 通过控制器或路由, 这是物品评级更新的地方:
/ / app /线路/球员.js
导出默认灰烬.路线.扩展({
行动:{
update: function(params) {
Var技能=参数.项,
评级= params.评级;
技能.设置(“分数”,评级);
返回的技能.save ();
}
}
});
发生这种情况时,此更改将通过传递到的绑定向下传播 星级
组件,因此显示的全星数量会发生变化.
这种方式, 突变不会发生在成分中, 由于唯一与应用相关的部分是对路由中动作的处理, 组件的可重用性不会受到影响.
我们也可以在足球技能中使用相同的组件:
{{#每个玩家.技能为|技能|}}
{{星级项目=技能等级=技能.分数集Action = " updateSkill "}}
{{/每个}}
重要的是要注意一些(大多数?我看到别人犯的错误(或者我自己犯的错误), 包括我在这里写过的那些, 是会消失还是会在21世纪早期得到极大的缓解.余烬x系列.js.
剩下的是我上面的建议,所以一旦你在灰烬 2中开发.X,你就没有借口再犯错误了! 如果你想要这篇文章的pdf格式, 去我的博客看看 点击文章底部的链接.
我来到了前端世界 灰烬.js 两年前,我将留在这里. 我对灰烬充满了热情,我开始在我自己的博客上写博客, 以及在会议上发言. 我甚至写了一本书, 与烬一起摇滚.js,适合任何想学习烬的人. 您可以下载一个示例章节 在这里.
在TDD流行之前,Balint就一直在实践它. 他是一个典型的PHP程序员,后来转向Java、Python和Ruby.
世界级的文章,每周发一次.
世界级的文章,每周发一次.