Riot.js riot.mount源码解析

628 阅读3分钟

介绍

riot.mount是riot加载模板的内容并结合传入的数据渲染出dom并更新到页面上的入口函数。

用法

最常用的用法:

  • selector (字符串)样式选择器,id、类名、标签选择器都可以
  • tagName (字符串)需要被加载的模板文件的名字,注意这里并不是文件的名字,而是模板内容中最顶层的tag名字,如:

  • opts (对象)传入的数据,类似react的props,[]表示可选

demo

先写一个最简单的demo(注意这里使用了necoo-loader分析):

todo.tag 内容:

<todo h="1">
    <div class="{cls}">{name}: {desc}{console.log('hello world')} {myFunc()}</div>
    // logic comes here
    let self = this;
    self.desc = '真帅';
    self.name = 'HongRunhui'
    self.myFunc = function () {
        console.log('你好');
    };
    self.cls = 'content';
    function beforeBeforeMount() {
        window.necooPush(arguments);
    }
    beforeBeforeMount();
    self.on('before-mount', function myBeforeMount() {
        window.necooPush(arguments);
    });
    self.on('mount', function myMount() {
        window.necooPush(arguments);
    });
    self.on('update', function myUpdate() {
        window.necooPush(arguments);
    });
    self.on('updated', function myUpdated() {
        window.necooPush(arguments);
    });
</todo>

执行轨迹图:

现象分析:

我用红色箭头标出了比较重要的几个步骤,黄色箭头则是我们上面todo.tag中埋下的钩子函数,可以看到beforeBeforeMount是早于myBeforeMount函数的,另外myMount是晚于myBeforeMount。所以在riot中的生命周期函数是这样的:

  • before-before-mount (实际上没有这个,这里我是用来表示直接写在script标签里的函数)
  • before-mount
  • mount

以及发现在mount的过程中并没有触发self.on('update')self.on('updated');

代码分析:

为了方便大家理解,我直接把上述标出的一系列比较重要的函数给列出来简单解释一下:

  • mount 入口函数
    • ? 选择要被加载到的父元素
    • pushTagsTo 一个包含递归的函数,将?中选择出来的节点进行循环加载tag标签
    • mount$1 开始真正加载某一个具体的模板tag
      • createTag 根据__IMPL_缓存下来的模板信息新建一个tag实例
        • componentMount tag实例创建好之后,开始将tag实例更新到dom中
          • parseAttributes 解析?选择出来的父元素上的属性,并保存到tag实例上
          • walAttributes 解析__IMPL_模板中已经解析好的attrs属性(如todo标签上的属性),并保存到tag实例上
          • parseAttributes 解析上一个步骤获取到的属性,如果有表达式,就丢到一个表达式数组里保存起来,如果没有,就同步到?选择出来的父元素上。
          • updateOpts 更新传入的opts
          • mixinwindow.__global_mixin上的方法绑定到tag实例上。
          • beforeBeforeMount 执行script里的js,这个时候我们写在todo.tag中这么写的数据let self = this; this.myLists = [1,2,3]中的myLists就会绑定到tag实例上。
          • myBeforeMount 如果有监听这个,这个对应的函数就会被触发
          • parseExpressions 通过遍历模板的HTML节点,将模板中的包含{title}这类的表达式给解析出来,存放到一个表达式数组变量expressions
            • componentUpdate 解析上面的expressions数组,使用new Function来获取tag实例上的变量值,生成最终包含数据的字符串模板,插入到dom中,最终将这个dom插入到?选出来的父元素中。

以上就是是大致的流程。

{xxx}
=>
Hello

这种模板语法最终是怎么替换成有数据的的呢?

举个栗子:

<div class="{cls}">{name}: {desc}{console.log('hello world')} {myFunc()}</div>

其中div内的内容都会被处理成这种函数并执行,来读取tag实例上的变量。

(function anonymous(E) {
    return [
        function _anonymous_15(v) {
            window.necooPush(arguments);
            v = (("name"in this ? this : window).name);
            return v || v === 0 ? v : ""
        }.call(this)
        ,
        ": ",
        function _anonymous_15(v) {
            window.necooPush(arguments);
            v = (("desc"in this ? this : window).desc);
            return v || v === 0 ? v : ""
        }.call(this),
        function _anonymous_15(v) {
            window.necooPush(arguments);
            try {
                v = ("console"in this ? this : window).console.log('hello world')
            } catch (e) {
                E(e, this)
            }
            ;return v || v === 0 ? v : ""
        }.call(this),
        " ",
        function _anonymous_15(v) {
            window.necooPush(arguments);
            try {
                v = ("myFunc"in this ? this : window).myFunc()
            } catch (e) {
                E(e, this)
            }
            ;return v || v === 0 ? v : ""
        }.call(this)
    ].join("");
})