由于公司内部使用regularjs,所以这两天在学习regularjs,做个笔记,方便查阅
一、模板语法
1、表达式
regular支持大多数的ES5表达式,如:
100 + 'b'
user ? 'login' : 'logout'
title = title + '1'
!isLogin && this.login()
items[index][this.nav(item.index)].method1()
注意事项:
1)this
指向的是组件本身,数据的根路径从组件实例对象的data属性开始(即component.data)
2)表达式中不支持++
、--
、位操作符等,也不支持正则表达式的字面量
3)regular开放了一些JS的内建函数,如:
Array
、JSON
、Date
、Math
、NaN
、RegExp
、Object
、String
decodeURI
、decodeURIComponent
、encodeURI
、encodeURIComponent
parseFloat
、parseInt
2、一次绑定
可以使用@(expression)
进行一次绑定
3、过滤器
过滤器语法:{expression|filter1|filter2|...}
注册一个过滤器:
Regular.filter('filterName', function(obj) {
// ...
});
4、Range
可以使用a..b
生成左闭右闭的区间(即[a, b]
),如1..5
相当于数组[1, 2, 3, 4, 5]
5、错误抑制
regularjs会抑制大部分xx of undefined
错误,并使用undefined
代替。但是函数内部的出错信息,是会保留的
6、插值
插值语法为:{expression}
属性插值:
1)如果属性是一个字符串,字符串中含有插值。那么解析后,会转化为相应的组合表达式。如:class="my-{className}"
,解析my-{className}
后会得到'my-' + className
,通过这个表达式来求取值
2)非指令类的属性,会在绑定值
改变的时候,自动更新属性值
。如class={myClass}
3)指令类的属性(包括事件),会将插值表达式传给directive
处理函数,交由directive
处理函数处理。如果r-model={checked}
7、内嵌组件
在编译阶段(AST -> DOM)中,当regularjs碰到一个节点的时候,如:
<custom-pager attr1={user} attr2=user on-nav={this.nav()}></custom-pager>
将会发生以下的过程:
1)流程一:
- 创建一个空data对象(
data: {}
) - 如果组件有子元素,那么子元素的内容会作为实例的
$body属性
- 遍历每个属性。
a. 如果属性是一个事件(on-xx
),那么注册为Emitter事件
,相当于this.$on(xx, ...)
b. 如果不是事件,则作为data
的一个属性值(data: {xxx: 'xxx'}
)。此外,在此的基础上,如果属性还是一个插值(class={myClass}
之类的),就建立父组件与子组件的双向绑定 - 初始化组件,
data
作为参数传入 - 插入到父组件的内容中
2)流程二:
- 创建一个节点(
document.createElement(tagName)
) - 编译子元素,并插入到节点中
- 遍历属性值,按不同类型处理
- 插入到父组件的内容中
8、条件逻辑
regular提供了if/elseif/else
的条件逻辑,用法如:
{#if cond1}
...
{#elseif cond2}
...
{#else}
...
{/if}
同时,除了可以控制快,还可以控制属性,即:
<div {#if module == 'home'}class="current"{/if}></div>
9、包含内容
可以使用{#include includedContent}
指令来包含需要动态引入的内容。和插值不同的是,每当includedContent
的发生变化时,就会重新编译解析,插入到指定位置。includedContent其实是一个Expression
,而求值的结果则是字符串或者模板AST。
10、列表渲染
可以使用{#list sequence as item}{/list}
的形式进行列表渲染。此外,可以通过item_index
获得下标
二、指令
regular在遇到一个属性的时候,会首先检查它是否是一个已经注册过的指令。如果是的话,那么就会交由directive
的link函数(回调函数)处理,如果不是的话,那么就视为一个普通的属性。
注意: 指令只能运用于节点,不能用于内嵌组件
link函数
link函数有三个参数,即link(elem, value, attrs)
1)elem
指令绑定的节点
2)value
,指令可能有的值。分为以下的情况
- 值不是插值的时候(如
r-model="Hello"
),value是一个字符串(这里得到的是Hello
) - 值是插值的时候,value则是一个expression对象
- 当没有值的时候,value则是空字符串
''
3)attrs
节点上的属性列表
4)this
,link函数中的this
指向的是组件
可以使用
value.type
来判断一个value是不是expression对象。如果是一个expression对象,则再link函数内使用this.$get(value)
即可求值
三、组件
组件是一种独立的、可复用的交互元素的封装。在regular中,组件 = 模板 + 数据 + 业务逻辑
示例中,命名Component
表示一个接口同时属于Regular
及其子类。而命名Regular
表示一个接口只属于Regular
本身(全局配置参数)
1、定义组件
定义组件,使用Component.extend(options)
。定义后,Component将成为父组件,子组件和该组件有继承上的关系。而options中定义的所有属性,都会成为子组件的原型属性。其中,options是一个配置对象,它具有以下的属性:
template
指定模板config(object data)
初始化参数(编译模板之前调用)init
会在模板编译之后(已经产生LivingDOM)调用,所以与DOM相关的逻辑可以在此处理destory
如果需要自定义回收逻辑,可以重写destroy方法
var Component = Regular.extend({
// ...
destory: function() {
this.supr();
// 其他逻辑
}
})
name
注册组件到父组件的命名空间内,得以被内嵌使用。如:
var Component = SuperComponent.extend({
// ...
name: 'foo1'
})
var Component2 = SuperComponent.extend({
template: '<foo1></foo1>'
});
此外,命名一个组件,还可以是使用时指定,如:
var Component = SuperComponent.extend({
// ...
});
SuperComponent.component('foo1', Component);
不过,Component.component
的方式指定,要比第一种方式更强大。因为,该种方法注册一个组件到父组件的命名空间内,可以是任意组件。如:
var Component = Regular.extend({
// ...
});
// 这里Component并非SuperComponent子类,但是仍然可以注册到SuperComponent的命名空间内
SuperComponent.component('foo1', Component);
events
在组件初始化之前声明要绑定的事件,如:
Regular.extend({
events: {
'$init': function() {
// 和 component.init 是一样的
},
'$destroy': function() {
// 和 component.destroy 是一样的
}
}
});
data
要传入的数据
2、使用Component.implement(mixin)避免深层的继承
Component.implement
是用来扩展Component自身的原型方法的(所以和Component.extend
不同的是,Component.implement
返回的是组件本身,而非子组件)
3、初始化组件
初始化一个组件的方式有两种:通过JS初始化
、模板中初始化
,但是两种方式是等价的。
1)通过JS直接初始化
var pager = new Pager({
data: {
current: '1'
}
});
pager.$on('nav', someCallback);
2)模板中初始化
<pager current=1 on-nav={someCallback($event)} />
这种方式就是内嵌组件
。之所以可以使用pager
这个名称,是因为在Pager的name里,指明了name
,即:
var Pager = Regular.extend({
name: 'pager',
// ...
});
4、内嵌组件的注意事项
1)默认传入的属性,都会成为data的成员,如:
<pager total=100 current={1} show={show}></pager>
就会有:data.total=100
,data.current=1
,pager.data.show=this.data.show
(this指向模板所在组件)
2)如<pager show></pager>
,这种情况下,value的默认值是true
3)连缀格式会转为驼峰,即<pager show-modal={true} />
,那么show-modal
会转化为showModal
4)on-[eventName]
转为组件事件绑定,如:<pager on-nav={this.nav($event)}></pager>
,相当于pager.$on('nav', this.nav.bind(this))
四、事件
regular中,事件分为两类:组件事件
、DOM事件
1、DOM事件
事件绑定的语法为:on-eventName={Expression}
,事件触发时,Expression就会运行一次。
示例:<button on-click={count = count + 1}>INCREASE</button>
阻止事件的方式有两种:{Expression}返回false(即 === false)
,或者使用$event.preventDefault()
2、自定义事件
使用Component.event(event, fn)
可以注册一个事件,参数说明:
event
自定义的事件名称(如tap
、hold
)fn(elem, fire)
elem是绑定的节点,fire是触发器
示例:使用Component.event()
定义enter
事件,处理回车逻辑:
var dom = Regular.dom;
Regular.event('enter', function(elem, fire) {
function update(e) {
if(e.keyCode == 13) {
e.preventDefault();
fire(e);
}
}
dom.on(elem, 'keypress', update);
return function destory() {
dom.off(elem, 'keypress', update);
}
});
使用方式:<textarea on-enter={this.submit($event)}></textarea>
3、事件代理
regular中支持事件代理,主要使用delegate-*
,delegate-*
会绑定到组件的第一父元素上
4、$event
可以使用$event
来获取事件对象,该变量是临时定义在data.$event
上的,所以可以直接在模板中使用$event
。对于非自定义事件,$event
传入fire的对象。在兼容IE6的情况下,$event
对象有如下的属性和方法($event
对象是修正过的):
origin
:绑定节点target
:触发节点preventDefault()
:阻止默认事件stopPropagation()
:阻止事件冒泡which
、pageX
、pageY
、wheelDelta
、event
(事件原始对象)
5、组件事件
component.$on(event, fn)
用于添加事件component.$off(event, fn)
用于解绑事件component.$emit(eventName [, agrs...])
用于触发某个事件
1)模板里声明绑定
示例如:<pager on-nav={this.refresh($event)}></pager>
2)声明周期中的事件
$config
会在编译之前触发$init
会在编译后触发,此时DOM结构已经生成了$destroy
会在组件被销毁时触发
6、事件的共性
对于on-xxx
传入的属性值,是插值还是普通属性,regular会做不同的处理。如:
on-click={this.remove()}
传入的是表达式,所以只要事件发生,表达式就会运行on-click="remove"
当传入的不是表达式,事件会代理到组件的事件系统中,此时可以用$on
处理事件。如:
var Component = Regular.extend({
// ...
init: function() {
this.$on('remove', function(e) {
// 处理逻辑
});
}
});
组件事件和DOM事件
1、组件事件是由$emit
方法抛出的,DOM由用户触发、浏览器抛出
2、DOM事件有冒泡机制,而组件事件没有
3、$event
对于组件事件来说,是$emit
传入的一个参数。而DOM事件的$event
则是封装过的事件对象
五、动画
regular
的动画实现如:<div r-animation="on: click; class: animated fadeIn; wait: 1000; class: animated fadeOut"></div>
,这段的意思为:
1)click时触发动画,添加类名animated
和fadeIn
,而当transitionend
或者animationend
的时候,移除类
2)等待1000ms
3)添加类名animated
和fadeOut
,当transitionend
或animationend
时,移除类
4)结束动画
动画系统的Command
regular的动画系统中内建了多个Command。如:
1)on和when
on和when是用于激活动画开始的Command。其中,对于on:event
,当特定的event触发时,开始动画,event可以是当前节点的DOM事件,也可以是所在组件的事件。
regular内置两个事件(r-hide
、{#if}
、{#list}
、{#include}
、component.$inject
等导致节点的变化,都会触发enter
或者leave
):
enter
进入事件leave
离开事件
而when
,和on
的作用类似,只不过它是在when: expression
中的expression
为真时触发
2)class: classes, mode
classes
是一个空格分隔的类名mode
添加class的模式
其中,mode
的取值有:
1
(默认,添加类名到节点,动画结束后移除它)
2
(首先添加class到节点,然后在nextTick添加class-active
来触发动画,当动画结束,移除所有类名)
3
(动画结束后,不移除类名)
4
(移除指定类名,并等动画结束)
3)emit:event
抛出某个事件,可能会导致触发另一个动画序列
4)call:expression
运行一个表达式,并进入脏检查
5)style: propertyName1 value1, propertyName2 value2 ...
设置样式,并且等待transitionend
6)wait: times
等待数秒,再进入下一个步骤(单位为ms)