愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

RegularJS学习总结(一)

由于公司内部使用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的内建函数,如:

  • ArrayJSONDateMathNaNRegExpObjectString
  • decodeURIdecodeURIComponentencodeURIencodeURIComponent
  • parseFloatparseInt

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=100data.current=1pager.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 自定义的事件名称(如taphold
  • 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():阻止事件冒泡
  • whichpageXpageYwheelDeltaevent(事件原始对象)

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时触发动画,添加类名animatedfadeIn,而当transitionend或者animationend的时候,移除类
2)等待1000ms
3)添加类名animatedfadeOut,当transitionendanimationend时,移除类
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)