4

riotjs

riotjs一款小型的10000star mvp框架。目前进化至3.x版本了。读者注意,本篇文章介绍的是2.2.4哦。为啥介绍这款啊,是因为那个啥,preact面向现代浏览器,对我来说不咋好使。

riotjs从出生到现在总共经历了3个大版本,基本上每个都不一样,1.x最为简陋,可以视之就一个简单的mvc框架哦,模板引擎也是简单的不要不要的,2.x版本完善了各项功能,并且强化了controller的作用,使之成为一个真正的MVP框架。3.x版本使用了大量es6,es5新增方法进行重构,对svg支持,模板引擎,事件系统,内存使用等进行了一定程度的优化。(实际从2.3开始就往现代浏览器上靠了)

为何选用

由于riotjs小,容易和其他框架混合使用

特点

小,但经不起强渲染

支持ie8吗

riotjs 2.2.4是最后一个支持ie8的版本。(然而事实上,代码中使用了一些es5新增的方法,这些方法要ie9才支持,以至于我们不得不使用es5shiv/es5sham来进行兼容);

静态方法

riot.observable

riot的事件系统,所有事件通知方式都基于该模块,可全局使用

// 发送全局事件

var window.eventBus = riot.observable();

eventBus.on('test', function (e) {
    console.log(e);
});

eventBus.trigger('test', 123);

包含方法

  • on(events, fn)
  • off(events, fn)
  • trigger(name[,arguments])
  • one(name, fn)

riot.mixin

作用是向内部对象mixins添加属性或方法,该对象无保护,所以必须要人为保证命名时不冲突.

// 在外部使用
riot.mixin('testfunction', function () { console.log(2) });

var c = riot.mixin('testfunction');

c() // print 2

该方法一般提供给riot tag初始化实例的时候使用。当在tag类中使用this.mixin混入方法的时候,会将内部对象mixins上的方法或属性混合到tag类上


riot.route

2.2.4版本的riot.route是一个功能超弱的路由管理器,通过监听hashchange事件来触发注册的路由回调。该路由模块是自动启动的。而且它的实现上是有缺陷的。它本质上是个事件分发器。

// 使用示例

riot.route(function (path, module, action, params) {
    console.log(path, module, action, params)
});

riot.route('search/index/search/1234');

包含方法

  • riot.route(arg)

    • 2.2.4版本里arg接受2种类型,字符串和function,上面已经给出示例。需要自己去分出路径,模块,行为和参数。你没看错,就是这么弱
  • riot.route.exec(fn)

    • 解释当前哈希路径,并把参数传递到fn里
  • riot.route.parser(fn)

    • 指定哈希路径解释器,如果未调用该方法,固定解释方法是 path.split('/');如示例所示
  • riot.route.stop()

    • 销毁监听hashchange事件,销毁路由事件
  • riot.route.start()

    • 监听。默认是开启的

riot.util

包含两个内容,brackets和tmpl, brackets是tmpl的辅助函数,单独使用意义不大,该辅助函数可以通过正则或者索引制造我们需要匹配的部分。tmpl是riotjs的模板引擎核心,html字符串拼接完全通过该引擎,可独立使用(在npm上有独立维护的模块名为riot-tmpl)

// 设置模板占位符(默认是{ })
riot.settings.brackets = '{{ }}';
// 使用
var html = riot.util.tmpl('<div>{{a}}</div>', { a:1 });
console.log(html);

//print <div>1</div>

riot.tag(name, html, css, attrs, fn)

全局注册一个riot标签, css attrs参数可省略。其实质是向一个内部对象tagImpl上创建了一个名为name的属性,其值是{name,html,css,attrs,fn}。此时该缓存并没有被使用,tag的实例并没有建立。

riot.tag(
    'ri-root',
    [
        '<ri-login if={showLogin}></ri-login>',
        '<ri-error if={showError}></ri-error>'
    ].join(''),
    function () {
        var self = this;
        this.showLogin = false;
        this.showError = false;
        this.on('mount', function () {
            var device_id = window.Qutils.getParams('device_id');
            if (!device_id) {
                alert('参数device_id缺失!');
            }
            else {
                setTimeout(function () {
                    bridge.isInApp(
                        function () {
                            self.showLogin = true;
                            self.tags['ri-login'].trigger('login-init', device_id);
                            self.update();
                        },
                        function () {
                            self.showError = true;
                            self.update();
                        }
                    )
                }, 100);
            }
        });
    }
);

riot.mount & riot.mountTo

riot.mountTo只是riot.mount的别名。该方法顾名思义,挂在riot标签(组件)。会返回一个tag的实例。

参数

  • selector

    • 接受'*'(mount所有), string split with ',' , string(使用原生的Selectors API,获取一个NodeList),或者接受一个NodeList,Element
  • tagName

    • 接受'*'(mount所有), object(当为Object类型时,即为opts),string(等同mount所有selector上下文下的tagName匹配tag)
  • opts

    • Object,传入的参数对象,可直接混合在tag实体的opts对象上
<!-- example -->
<div id="test"></div>
<script>
    var tag = riot.mount('#test', '*', {a:1,b:2});
    console.log(tag[0].opts.a) // print 1
</script>

riot.update

更新所有的tag实体,实质是调用每个实体的update方法。

riot的tag实例方法

上文说到riot中所用通过riot.tag声明的custom tag都只是缓存了,而没有立刻产生tag实例。实际上tag实例是在执行riot.mount的时候被创建的。所有的riot tag实例都是由内部构造器Tag实例化而来的。而对于一个多tag嵌套的组件,其实是递归先将子tag从底部实例化完,当实例化完成,会从根部到底部依次触发mount事件~

this.isMounted

true | false, 指示tag是否完成安装

this._id

一个自增的id,用于唯一代表该实例

this.parent

若一个tag实例有父实例,这个parent指向父实例

this.on('mount', function () {
    this.parent && this.parent.trigger('child-mounted');
}.bind(this))

this.root

该属性指向tag实例所表示的真实dom元素,另外root._tag同样挂载了tag实例的引用,所以当你的个自定义标签实例化以后,你还可以通过这样的姿势找到tag实例

var tag = riot.mount('custom-tag');

console.log(tag[0].root);
// print <custom-tag></custom-tag>

console.log(document.querySelector('custom-tag')._tag)
// print Tag {xxx}

this.opts

哦,这个的构造器是Child,不过特的原型指向你传入的opts的引用。所以如果不想自己配置被改动,请乖乖深度克隆

var tag = riot.mount('custom-tag', {a:1,b:2,c:3});

console.log(tag[0].opts.a);
// print 1

另外如果要在父子tag间传递参数也是很好玩的.

riot.tag(
    'hhh',
    '<zzz myoptions={this.opts.test}></zzz>',
    function () {}
);
riot.tag(
    'zzz',
    '<div>{this.opts.myoptions}</div>',
    function () {}
);
var new_custom_tag = document.createElement('hhh');
document.body.appendChild(new_custom_tag);
var custom_tag = riot.mount('hhh', {test:1});
console.log(custom_tag[0].tags['zzz'].opts.myoptions);
// print: 1

// 注: riot没有state机制,要通过attributes传值,小心undefined

需要注意的是,由于他遍历的是dom.attributes,你玩表单的时候小心一点。

this.tags

这里面放了子tag的实例的引用,上面的示例中有类似用法。多个同名子tag会放在数组里,我记不得在哪个版本里了,即使是一个子tag也会放在数组里。

this.update(data)

这个操作分为几个步骤:

一:源码里明确写到,执行该方法先判断data对象里有没有可能覆盖tag实例属性的属性,如果有,丢弃。注意,该处过滤数据使用的是浅复制,如果是对象套对象,你要小心了。

二:如果是循环产生的tag(注意,如果不是在custom-tag上使用each循环产生,不会去继承,因为此时isLoop为undefined),重新从父tag获取需要继承的值

riot.tag(
    'hhh',
    '<zzz each={eee} myoptions={opts.test}></zzz>',
    function () { this.eee = [{a:1,b:2},{a:1,b:2}]; this.eeeeeeeeee = 43214321; this.fdsafsdf = 3253425432 }
);
riot.tag(
    'zzz',
    '<div>{a} {b}</div>',
    function () { this.fffff = 4325345342543}
);
var new_custom_tag = document.createElement('hhh');
document.body.appendChild(new_custom_tag);
var custom_tag = riot.mount('hhh', {test:1});
console.log(custom_tag[0].tags.zzz[0].fdsafsdf)
// print 3253425432
riot.tag(
    'hhh',
    '<div each={eee}><zzz myoptions={opts.test}></zzz></div>',
    function () { this.eee = [{a:1,b:2},{a:1,b:2}]; this.eeeeeeeeee = 43214321; this.fdsafsdf = 3253425432 }
);
riot.tag(
    'zzz',
    '<div>{a} {b}</div>',
    function () { this.fffff = 4325345342543}
);
var new_custom_tag = document.createElement('hhh');
document.body.appendChild(new_custom_tag);
var custom_tag = riot.mount('hhh', {test:1});
console.log(custom_tag[0].tags.zzz[0].fdsafsdf)
// print undefined

该特性可能会对您造成困扰,啥时候误操作都可能一头雾水不造为什么。

三:混合其他数据和属性到当前tag上

四:更新视图,删除dom传导属性,重置事件。(所以说如果你浏览器在dom回收和事件回收上有问题,那你更新的时候就相当捉急了,在最新的版本里把这个泄漏点给堵上了)

this.mixin()

接受无数多个字符串参数,内部运行 riot.mixin[arguments[i]]将需要混入的属性或方法混入到实例上~~前面介绍过了。在2.2.4以前的版本没有使用.bind(this)来参入作用域,略蛋疼的说,2.2.4 对混入的方法都bind了当前作用域。另外,注意init是很特殊的,在混入时会自动执行。

riot.mixin({
    init: function () {
        this.a = 1;
        console.log('init')
    }
});

riot.tag('test', '<div></div>', function () {
    this.mixin('init');
});

var a = document.createElement('test');
document.body.appendChild(a);
var custom_tag = riot.mount('test');
console.log(custom_tag[0].a)
// print 1

this.mount()

将该实例强制重新装载一遍

this.unmount(keepRootTag)

传入参数,如果为true,会把初始化用的那个根节点也删球掉。该方法用于卸载实例,释放内存。

on, off, trigger, one

通过riot.observable混入的事件方法,然后我们可以在不同tag实例上到处传播事件了,建议使用一个集线器把事件管理起来,或者使用其他玩意,比如riot-flux什么的来玩。

生命周期

to be continue

后记

这个框架跟虚拟dom没撒关系,因为完全没有diff算法。。。在更新视图的时候用了文档碎片凑~,效果还凑合


geeeger
459 声望35 粉丝

我是一个智障,你害怕了吗?我的破文章如果对您有用的话,请赏一个,感谢