component顾名思义,是ember中的组件。其特性为可以重复利用。component也是由两部分组成,一个handlebars的模板文件以及一个js文件。其中模板文件控制如何渲染,js文件控制它的行为。
组件的定义
类似的,定义component,也可以使用ember-cli的命令行工具:
ember g component <your-componetn-name>
这样就能在你定义的目录下,创建一个component
。component
是可以重复使用的,意味着,我们可以在其他的component
或者是route
中使用它,使用时很简单,使用component
helper就好。
我们在此举例:(1)创建一个名字为component-demo
的component,然后在模板文件中写下如下的内容:
//app/templates/components/component-demo.hbs
<div>this is a component start</div>
{{yield}}
<div>this is a component end</div>
下面尝试在component-parent中进行调用。
//app/templates/components/component-parent.hbs
{{#component-demo}}
<div>add content to the component</div>
{{/component-demo}}
这样,就可以将<div>add content to the component</div>
渲染到{{yield}}
中去(这个也叫作’包裹内容’)。
传递参数
我们都知道,如果没有数据的话,就无法正常渲染,同理,组件也需要进行传参,这就要求组件必须要接受参数、返回参数的能力。
接受参数
component接受参数十分简单,只需要在helper中进行传入即可。
举例而言,我们有一个名为blog-list
的component,其要做的事情,是渲染所有的博客列表,其中博客列表以一个array
的形式传入:
//app/templates/components/blog-list.hbs
<div>this is the application start</div>
{{#each blogs as |blog|}}
title:{{blog.title}}
content:{{blog.content}}
{{/each}}
<div>this is the application end</div>
然后在调用的地方,我们可以在一个routeblog-route
中这么定义:
//app/templates/blog-route.hbs
{{blog-list blogs=model}}
//app/routes/blog-route.js
export default Ember.Route.extend({
model(){
return [
{
title:'blog1',
content:'content1'
},
{
title:'blog2',
content:'content2'
}
]
}
});
这样,就可以渲染出如下页面:
这也就实现了数据的传入。
除此以外,还可以通过入参位置进行指定。在此不作详细探讨,而且不推荐那样使用,可以参考:[ember component](https://guides.emberjs.com/v2.11.0/components/passing-properties-to-a-component/)
传出参数
除了给component传入参数之外,我们往往也有需求,就是将component的数据向上传递。
首先,我们要确定的是,什么情况下,数据需要向上传递。一般情况下,数据是双向绑定的,这意味着,如果我们只是修改传入的数据的话,那么我们就无需去传出,因为你直接使用传入的参数的就可以。
那么什么时候需要传出呢?
方案1
我们还是以上述的component为例,其中component中js如下:
export default Ember.Component.extend({
modelLength:Ember.computed('model',function(){
return model.length;
});
});
这个length返回传入参数的长度,我们先不考虑这个绣球有没有这个需要,只考虑如何将这个length调用他的route或者component中去。
方法是有的,首先介绍一下一种方式,就是以事件的方式向上冒泡,这个方法使用是有一定的条件的,就是必须要在component中定义一个事件,如click、hover、change
等,主要就是调用sendAction()
方法,还是以刚才的component为例:
//app/templates/components/blog-list.hbs
<div>this is the application start</div>
{{#each blogs as |blog|}}
title:{{blog.title}}
content:{{blog.content}}
{{/each}}
<button type="button" {{action 'submitAction' }}>提交</button>
<div>this is the application end</div>
其中js代码为:
export default Ember.Component.extend({
actions:{
submitAction(){
let len = this.get('modelLength');
this.sendAction('submitLen',len);
}
}
});
其中在route中调用component的代码:
{{blog-list submitLen='routeAction'}}
其中在route中获得传参的代码:
export default Ember.Component.extend({
actions:{
routeAction(param){
alert(param);
}
}
});
这样,就可以进行参数的传递,但是很明显,这种方式,需要有事件才能进行传递,如果事件没有触发,那么就无法获取到component中的参数。不过,正是由于Ember中数据是双向绑定的,所以说,这种情况下,基本已经满足大部分的需求。但是还是有一部分情况无法满足。
方案2:使用service
这种时候,就需要动一些脑筋。一般情况下,使用全局的变量来进行数据的保存,是比较正常的做法。
说道全局的概念,在ember中,有一个service的概念,是在初始化后,就是全局的,可以利用这个进行全局变量的保存。
这里先定义个service
,同样是使用ember-cli来进行创建一个名为blog-service
的serivce:ember g service blog-service
。service会在app/services
目录下创建一个blog-service.js
文件,现在我们在service中定义一个全局变量以及一个全局方法:
//app/services/blog-services.js
export default Ember.Service.extend({
data:[],
addData(data){
this.get('data').pushObject(data);
},
getData(){
return this.get('data');
}
});
在component中调用service中的方法:
export default Ember.Component.extend({
blogService:Ember.inject.service('blog-service'),//注入service,有点类似于依赖注入,是单例的。
actions:{
routeAction(){
this.get('blogService').addData(123);
this.sendAction('routeAction');
}
}
});
在route中进行调用:
export default Ember.Route.extend({
blogService:Ember.inject.service('blog-service'),
actions:{
routeAction(){
alert(this.get('blogService').getData());
}
}
});
这样,route中就可以拿到这个component放到service中的变量。当然,service还有更加灵活的用法,只要注意是全局的就好。
一般情况下,我们不建议使用这种方式,因为这样的话,component的复用就会成问题,因为每次component调用都是创建新的实例,但是service是单例的,所以,变量也是全局的,不管你如何控制,可能会破坏component的独立性。此外,如果事件传递的方式无法解决问题的话,很大程度上是因为compoent设计不合理导致的,一般情况下,能够通过数据双向绑定解决的,那么就不要用其他的方式。
这里可以考虑一个场景,一个route循环调用一个component10次,那么service就没法很好的管理component向其中插入的变量。但是,如果你在使用的时候,每次最多只实例化一个component,那么就没有问题。
定制component
在实际的渲染过程中,我们会发现,默认情况下,ember会给component外面加上一个div
元素(如下所示),这个有时会给我带来麻烦。典型的情况,就是如果我们想要创建一个行内元素的componet,类似于input、img
等,那么就没有办法很好的创建。
//ember 渲染出的元素
<div id="ember180" class="ember-view">
<h1>My Component</h1>
</div>
ember在这里提供一个解决方案,可以定制你自己的compoent。
替换元素名、类
例如,默认component的最外层元素为div
元素,如果我们想使其变为nav
,我们可以在component的js文件中进行修改,并且指定class
为primary my-class-name
,那么可以如下完善:
export default Ember.Component.extend({
// 使用tabName属性指定渲染之后HTML标签
// 注意属性的值必须是标准的HTML标签名
tagName: 'nav',
classNames: ['primary', 'my-class-name'] //指定包裹元素的CSS类
});
替换元素属性
如果想要增加元素的属性,例如我们想要添加一个href
属性或者是name
属性,那么可以如下操作:
export default Ember.Component.extend({
attributeBindings: ['href','name'],
href: 'www.baidu.com',
name: 'linking'
});
处理事件
component
可以处理丰富的事件,其中包含的事件,详细可以看官方资料。下面简要介绍一下如何处理事件。
component级别的处理
如果component
的处理,例如鼠标时间,ember中支持
- mouseDown
- mouseUp
- contextMenu
- click
- doubleClick
- mouseMove
- focusIn
- focusOut
- mouseEnter
- mouseLeave
如果是在component的最外层进行调用事件,以click为例,那么js文件如下:
//app/components/your-component.js
export default Ember.Component.extend({
click(event){
alert($(event.target).html())
}
});
模板文件为:
//app/templates/components/your-component.js
{{yield}}
<div>content1</div>
<div>content2</div>
route中调用如下:
{{#your-component}}
<div>route-content</div>
{{/your-component}}
那么分别点击content1
content2
route-content
,页面展示为
如果说点击最外层父元素的话,展示为:
总之,其他的也是一样的。
组件内元素事件
如果组件内某一个元素,例如一个按钮,需要添加事件,那么需要如何操作呢?以your-component
为例:
//app/templates/components/your-component.hbs
{{yield}}
<button type="button" name="button"></button>
在这个button的基础上,我们分别给他添加上click
, mouseEnter
, mouseLever
事件。
click事件
//app/templates/components/your-component.hbs
{{yield}}
<button type="button" name="button" {{action 'clickButton'}}>button</button>
//等同于:<button type="button" name="button" {{action 'clickButton' on='click'}}>button</button>
js代码为:
//app/components/your-component.js
export default Ember.Component.extend({
actions:{
clickButton(){
alert('click event');
}
}
});
mouseEnter、mouseLeaver
//app/templates/components/your-component.hbs
{{yield}}
<button type="button" name="button" {{action 'enterButton' 'enter' on='mouseEnter'}} {{action 'leaveButton' 'leaver' on='mouseLeave'}}>button</button>
js代码为:
//app/components/your-component.js
export default Ember.Component.extend({
actions:{
enterButton(){
console.log('enterButton');
},
leaveButton(){
console.log('leaveButton');
}
}
});
一个元素上是可以绑定多个action
的,默认的事件为click
事件。
给action传递参数
以click
为例
//app/templates/components/your-component.hbs
{{yield}}
<button type="button" name="button" {{action 'clickButton' '1' on='click'}}>button</button>
js代码为:
//app/components/your-component.js
export default Ember.Component.extend({
actions:{
clickButton(params){
alert(params);
}
}
});
这里的params
就是1
,注意参数只能传递一个,不能传递多个,且顺序不能乱了。
冒泡事件
如果想要向上冒泡事件,可以使用sendAction()
这个方法。
以click
为例
//app/templates/components/your-component.hbs
{{yield}}
<button type="button" name="button" {{action 'clickButton' '1' on='click'}}>button</button>
js代码为:
//app/components/your-component.js
export default Ember.Component.extend({
actions:{
clickButton(params){
this.sendAction('clickButtonComponent',params);
}
}
});
父route的hbs:
{{your-component clickButtonComponent='clickButtonRoute'}}
父route的js:
//app/routes/your-route.js
export default Ember.Route.extend({
actions:{
clickButtonRoute(params){
alert(params);
}
}
});
这样,当点击按钮的时候,就可以弹出弹框了。
最后需要注意一下,如果事件与component的事件重叠了,那么处理的顺序为,先处理自己的,在处理component级别的。
component的生命周期
在component中,我们免不了要接触到component的生命周期,理解component的生命周期,会使我们更加的理解如何更好的使用ember。
(1)初始化时
- init
- didReceiveAttrs
- willRender
- didInsertElement
- didRender
(2) 重新渲染时(rerender)
- didUpdateAttrs
- didReceiveAttrs
- willUpdate
- willRender
- didUpdate
- didRender
(3)销毁时
- willDestroyElement
- willClearRender
- didDestroyElement
我们一般不太关注销毁的内容,主要关注初始化和重新渲染的时候。
我们首先要知道,什么时候会初始化,什么时候会重新渲染。
初始化
初始化的顺序为:
- init :负责初始化内容
- didReceiveAttrs:负责格式化要展示的参数
- willRender:负责渲染前的动作
- didInsertElement:三方包的调用
- didRender:在已经渲染好的页面上新增元素
其中didReceiveAttrs
、willRender
、didRender
,在复写逻辑的时候,需要格外小心,因为在重新渲染的时候也有用到这个方法。
注意,如果你在willRender
之后,修改了页面中要渲染的数据,那么,在执行完didRender
后,就会进入重新渲染流程
重新渲染顺序
- didUpdateAttrs:负责更新参数
- didReceiveAttrs:负责格式化要展示的参数
- willUpdate:数据更新前的动作
- willRender:页面渲染前的动作
- didUpdate:更新三方包的元素
- didRender:在已经渲染好的页面上新增元素
这里需要注意,如果在didUpdate
或者didRender
里面增加数据更新的操作,那么会进入死循环!!!所以,你的操作,尽量要与方法的含义一致!