「组件化」不仅仅是标签化这种表现层面的描述
在用户端开发领域,组件应该是一种独立的、可复用的交互元素的封装。
Regular的组件满足以下等式
组件 = 模板 + 数据 + 业务逻辑
提示
- 本节会简单涉及到 DOM事件
使用 Component.extend 定义一个可复用的组件
const Component = Regular.extend( {
template: `<button on-click={count+=1}> 你点了{count}次按钮 </button>`,
config( data ){
data.count = 0;
}
});
extend
返回的是一个组件构造函数,你可以直接使用 new
关键字进行实例化
new Component().$inject(document.body)
new Component().$inject(document.body) //可以重复使用
参考
- api - $inject: 插入组件到指定节点位置,或恢复组件到游离状态(脱离文档)
除了通过new
实现命令式初始化,Regular 支持在别的组件模板中使用声明式调用,这是最常见的使用方式。
// 注册名为Component的全局组件
Regular.component('Component', Component)
new Regular({
template:`
<Component ></Component>
<Component/> <!-- 自闭合 -->
`
})
效果与上例完全一致
<script async src="//jsfiddle.net/leeluolee/nwds9g6c/embed/result,js/"></script>参考
因为上例这种注册到全局的方式比较普遍, Regular 支持在 extend 时,直接设置别名
Regular.extend({
name: 'Component' // 设置全局别名
//...
})
data 在 Regular 中是个特殊的实例属性,根据 索引:表达式 所述
数据根路径从 component.data 开始,即 {user} 其实获取的是 component.data.user
实际上在模板里也可以拿到实例上的其它属性,比如 {this.nickName}
拿到的就是实例上的nickName字段,例如
new Regular({
template:`
<div>{this.nickName}</div>
<!-- 相当于 this.data.userName -->
<div>{userName}</div>
`,
nickName: 'leeluolee',
data: {
userName: 'hzzhenghaibo'
}
})
这样看起来,似乎 data 仅仅只是个短写的语法糖而已。 其实不尽然,因为
只有data上的字段可以通过声明式属性传入
<Component title='hello' />
就相当于
new Component({
data: {
title: 'hello'
}
})
节点属性会自动成为data
对象上的字段。
<Component total=100 title='Hello' ></Component>
相当于data.total='100'
, data.title='Hello'
注意非属性插值,则传入类型都是字符串
如果存在插值则会双向绑定,如下例的title与外层的title
字段就形成了双向绑定
<Component title={title} show={true} />
例外:在这里 show={true}
比较特殊,因为表达式是一个静态值,所以不会产生双向绑定。
<Component show ></Component>
上例相当于 <Component show = {true} />
<Component show-modal={true} isHide={hide}/>
则 show-modal
会转换为 showModal
, 但是 isHide
这种驼峰式的写法仍然支持,并不做变换
满足on-[eventName]
的属性会自动转换为组件事件绑定
<Component on-nav={this.nav($event)}></Component>
特殊属性如ref
和isolate
会有特殊的功能。请参考 获取内部节点和组件 和 组件隔离
const Component = Regular.extend({
name: 'Component',
template: `
<h2>{title}</h2>
<button on-click={shared.count+=1}>点击shared.count+1: {shared.count}</button>
<button on-click={instance.count+=1}>点击instance.count+1: {instance.count}</button>
`,
data: {
shared:{
count:1 // 会影响到所有实例
}
},
config(data){
data.instance = {count: 1} // 每个实例独有
}
}) // 每个实例独有
参考
Regular 提供了几个常用的生命周期钩子,你可以实现指定方法来处理自定义逻辑
Regular.extend({
config( data ){
data.title = data.title || '默认标题'
}
})
- data: __合并传入参数__后的data对象
参考
在这个阶段,已经可以获取到生成的节点,但是仍然是游离状态的,未插入到实际DOM节点中
Regular.extend({
template: `<div ref=container>Hello</div>`,
init(){
console.log(this.$refs.container.innerHTML === 'Hello'); //true
}
})
参考
销毁函数 , 注意 如果要自定义回收函数,务必调用父类 destroy(this.supr
)来销毁掉一些通用部分(如事件监听,数据监听)
const Component = Regular.extend({
init(){
this.onScroll = function(){};
window.addEventListener('scroll', this.onScroll)
},
destory(){
this.supr(); // 千万别忘记
window.removeEventListener('scroll', this.onScroll)
}
})
请参考 @TODO
实例化时
Regular 提供了 r-component
用于处理动态组件的需求,这个组件使用is属性来决定使用什么组件来渲染
- is: 组件名
如下例,我们预先定义了MarkdownRender
与 HtmlRender
两个组件
Regular.extend({
name: 'MarkdownRender',
template: `<div r-html={mdcontent} ></div>`,
config(data){
this.$watch('content', (content)=>{
data.mdcontent = marked(content)
})
}
})
Regular.extend({
template: `<div r-html={content}</div>`,
name: 'HtmlRender'
})
使用r-component
来动态实例化组件,注意is属性是个动态插值
<input type="radio" value='MarkdownRender' r-model={type} /> 使用markdown
<input type="radio" value='HtmlRender' r-model={type} /> 是否使用mdrender
<r-component is={type} content={content} />
参考
在 Regular 中,声明式实例化的组件都具有一个this.$parent
属性,它指向直接父组件
const Example = Regular.extend({
name: 'Example',
init(){
console.log(this.$parent.name) // log 'App'
}
})
const App = Regular.extend({
name: 'App',
template: `<Example title={title} />`
})
const main = new Regular({
template: `<App title={title} />`,
data: {
title: 'main title'
}
})
直接父组件,决定了子组件执行的数据上下文。比如App接受的title
实际指的是main.data.title
,而Example
的title则指向app.data.title
(App的实例)。
默认情况下,父子组件之间会建立双向绑定
<input r-model={total} type=number/>
<pager total={total}></pager>
即我在上层组件修改一份数据会导致pager也进行了脏检查,而pager发生数据变化也会引起上层组件发生脏检查,这在有些时候不是我们想看到的,也会影响到组件的整体性能。我们或许希望pager与上层组件完全隔离,而完全通过事件来通信。
isolate
可以实现上述要求,例如上例如果修改为:
<input r-model={total} type=number/>
<pager total={total} isolate on-nav='nav'></pager>
那内嵌组件pager与实际就是完全隔离了,完全等同于JS初始化new Pager().$inject('input', 'after')
。
获取到组件内部的子组件和子节点是使用 Regular 的常见需求。
组件本身应该是完全数据驱动,dom操作应该交由指令处理 在理念上并没有错,但有时直接操作DOM会简单很多。所以经常有人在 init 中写出以下代码(此时内部节点已经产生)
Bad Case
Regular.extend({
template: `<div id='xxx'></div>`,
init: function(){
const elem = document.getElementById('xxx'); 返回null
}
})
这种做法并不会凑效,因为 init 时此组件并没有插入到文档中,游离状态的组件自然无法用document.getElementById
获取到它。
但我们可以使用 ref 来解决
ref 是个特殊的属性,你可以使用它来标记一个__节点或组件__.
const component = new Regular({
template: "<input ref=input> <component ref=component></component>"
init: function(){
this.$refs.input.value = 'hahaha'
this.$refs.component.show() // 调用子组件的方法
}
})
如上例所示,在 compile 之后(比如 init 生命周期里),就可以使用this.$refs
来获取到内部子节点或子组件了。
还是应尽可能使用数据驱动的方式来构建你的UI
ref属性与其它属性一样可以插值,这样在类似循环渲染的场景中会比较有用
<script async src="//jsfiddle.net/leeluolee/tqLew7ou/embed/js,result/"></script>除了ref,Regular还提供了 dom.element(component, needAll)
可用于获取组件内部的节点。
const dom = Regular.dom;
const component = new Regular({
template: `<div id='first'></div><p id='last'></p>`
init: function(){
console.log(dom.element(this)) // => div#first
console.log(dom.element(this, true)) // => [div#first, p#last]
}
})
说明
- 如果needAll为
true
,返回子节点数组。
ref
的优点是简单直观而且高效,除此之外还可以 获取组件 。 而 dom.element
的优势是不需要做主动的标记,可以提供ref
无法满足的能力,例如
const Component = Regular.extend({
template: '<div ref="container">Component</div>'
init: function(){
this.$refs.container // ...
}
})
// SubComponent 继承自 Component
const SubComponent = Component.extend({
'template': '<div>SubComponent</div>'
})
这就会出现问题,因为SubComponent
覆盖的模板并没有标记 container 节点。本质其实是因为 模板的控制权不在当前组件 。
这个时候就可以使用dom.element
就可以完美解决了
const Component = Regular.extend({
template: '<div>Component</div>'
init: function(){
console.log(dom.element(this));
}
})
// SubComponent 继承自 Component
const SubComponent = Component.extend({
'template': '<div>SubComponent</div>'
})
Regular 的模板和一些框架不同,它没有单节点限制,在描述结构时可以更加自由
Regular.extend({
template: `
<strong>Hello, </strong>
{username}
<em>Nice to meet you</em>
`
})
此时默认使用 Regular.dom.element 将只能获取到第一个节点,如果你需要获取到所有节点可以传入第二个参数,此时将返回包含所有节点的数组
Regular.dom.element(component, true);