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('{{a}}', { a:1 });
console.log(html);
//print 1
riot.tag(name, html, css, attrs, fn)
全局注册一个riot标签, css attrs参数可省略。其实质是向一个内部对象tagImpl上创建了一个名为name的属性,其值是{name,html,css,attrs,fn}。此时该缓存并没有被使用,tag的实例并没有建立。
riot.tag(
'ri-root',
[
' ',
' '
].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对象上
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
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',
' ',
function () {}
);
riot.tag(
'zzz',
'{this.opts.myoptions}',
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',
' ',
function () { this.eee = [{a:1,b:2},{a:1,b:2}]; this.eeeeeeeeee = 43214321; this.fdsafsdf = 3253425432 }
);
riot.tag(
'zzz',
'{a} {b}',
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',
' ',
function () { this.eee = [{a:1,b:2},{a:1,b:2}]; this.eeeeeeeeee = 43214321; this.fdsafsdf = 3253425432 }
);
riot.tag(
'zzz',
'{a} {b}',
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', '', 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算法。。。在更新视图的时候用了文档碎片凑~,效果还凑合