Snabbdom.js(一)

闲谈:在学vue的过程当中,假造dom应该是听的最多的看法之一,得知其是自创snabbdom.js举行开辟,故习之。

由于我事情处于IE8的环境,对ES6,TS这些学问的演习也只是浅尝辄止,而snabbdom.js从v.0.5.4这个版本后开始运用TS,所以我下载了0.5.4这个版本举行进修(厥后才发明能够直接下载最新的版本,去dist目次找编译好的文件即可,而且这个版本另有BUG,在新版本中得到了修改,发起人人照样下载最新版本举行进修)

统共写了四篇文章(都是本身的一些鄙见,仅供参考,请多多指教,我这边也会延续修改加更新)

  1. 引见一下snabbdom基本用法
  2. 引见一下snabbdom衬着道理
  3. 引见一下snabddom的diff算法和对key值的熟悉
  4. 引见一下关于兼容IE8的修改

github
ps:进修的目标是愿望将snabbdom.js实践到事情中去,思前想后,决议拿表格衬着来开刀,而且兼容了IE8
《Snabbdom.js(一)》

固然我也是站在伟人肩膀上举行进修,参考文章:

snabbdom入门运用

vue2源码进修开胃菜——snabbdom源码进修(一)

vue2源码进修开胃菜——snabbdom源码进修(二)

好了,前面说了那末多‘空话’,如今切入主题。

直言不讳,先总结一下,经由过程本身的实践,个人认为假造dom的完成思绪为:

经由过程js对象模拟出一个我们须要衬着到页面上的dom树的构造,完成了一个修改js对象即可修改页面dom的快速门路,避免了我们手动再去一次次操纵dom-api的烦琐,而且其供应了算法能够使得用起码的dom操纵举行修改。

关于基本用法的引见,英语好的完全能够去看一下它github的内容 snabbdom.js,我这边主假如纪录本身在实践过程当中的一些笔记及踩坑。

1.怎样援用

我这边照样以0.5.4版本举行解说

中心文件是:

  • snabbdom.js
  • h.js
  • vnode.js(最新版本改成tovnode.js)
  • htmldomapi.js
  • is.js(这个文件是用来供应函数来推断数据是不是为undefined,最新版本已没把它零丁拿出来了)
  • polyfill.js(我这边为了兼容IE8本身增加的文件)

有了这几个文件实在就能够运用snabbdom.js来衬着我们的页面。

固然另有很主要的模块文件:

  • style.js
  • props.js
  • eventlistener.js
  • class.js
  • attribute.js
  • dataset.js
  • eventlistener.js

这些模块划定了我们假造dom具有哪些才能,比方很主要的eventlistener.js使得我们能够在假造dom上增加事宜,它们都是我们不可或缺的。作者将其分离出来应该是想剥离出中心代码,使得我们能够依据本身的需求来定制响应的模块。

援用的时刻各个文件之间照样有一定递次的,我是如许援用的:(snabbdom.js是末了援用,辅佐型文件polyfill.js is.js得最早援用):

<script type="text/javascript" src="polyfill.js"></script>
<script type="text/javascript" src="is.js"></script>
<script type="text/javascript" src="htmldomapi.js"></script>
<script type="text/javascript" src="eventlistener.js"></script>
<script type="text/javascript" src="class.js"></script>
<script type="text/javascript" src="attributes.js"></script>
<script type="text/javascript" src="props.js"></script>
<script type="text/javascript" src="style.js"></script>
<script type="text/javascript" src="dataset.js"></script>
<script type="text/javascript" src="vnode.js"></script>
<script type="text/javascript" src="h.js"></script>
<script type="text/javascript" src="snabbdom.js"></script>
<script type="text/javascript" src="index.js"></script>

固然你也能够把一切文件举行紧缩兼并,代码中还能够运用模块化的体式格局举行援用相干模块;

ps:由于我们这边还没有运用模块化,所以我把源码中运用模块化的部份简朴的修改了一下;

模块化也就是将一个功用零丁写在一个js文件中供别的文件运用,会运用一个对象举行封装导出,并经由过程马上实行函数的闭包使得其不会污染别的作用域变量。

举例:
导出
//a.js

aModule={};

(function(aModule){
     aModule.init=function(){}
})(aModule)

导入

<script type="text/javascript" src="a.js"></script>
var init=aModule.init;

2.怎样运用

先从最简朴的例子来看看snabbdom.js是怎样运用的;

《Snabbdom.js(一)》

代码以下:

var snabbdom = SnabbdomModule;

var patch = snabbdom.init([ //导入响应的模块
    DatasetModule,
    ClassModule,
    AttributesModule,
    PropsModule,
    StyleModule,
    EventlistenerModule
]);

var h = HModule.h;

var app = document.getElementById('app');

var newVnode = h('div#divId.red', {}, [h('p', {},'已转变')])

var vnode = h('div#divId.red', {}, [h('p',{},'2S后转变')])

vnode = patch(app, vnode);

setTimeout(function() {
    vnode=patch(vnode, newVnode);
}, 2000)

上面代码的主要功用就是衬着,经由过程snabbdom模块的init要领返回的patch函数完成,细分的话能够分为初始化衬着和对照衬着;

  1. 第一次是初始化的时刻,vnode=patch(app,vnode),app作为一个被替代的实在dom传入,返回一个当前页面的vnode,作为下一次衬着的对照假造dom。(这里须要注重的是,app是在这里作为一个替代dom,衬着后app将会被替代);
  2. 第二次是对照衬着,vnode=patch(vnode, newVnode);

上面的h函数是一个重点,它内里的内容实在就是页面dom元素的一个笼统:

 h('div#divId.red', {}, [h('p',{},'2S后转变')])

 // <div id="div" class="red>
 //   <p>
 //     2S后转变
 //   </p>
 //  </div>

h(sel,data,children)

  1. 它的第一个参数是元素的选择器,这里能够参考jq的写法,#.离别代表了id和class,关于多个class,能够div#divId.red.blue.black如许去写;
  2. 它的第二个参数是模块数据的定义,没有能够省略;
  3. 它的第三个参数是其子节点的情势;

         假如它无子节点,则为空,不写:h('p')
         
         假如它的子节点是文本节点,则直接写其字符串:h('p','2S后转变')
         
         假如它的子节点是包括元素节点,则须要用数组写入:
         (哪怕只要一个元素,数组内里还能够包括文本节点)
            h('div#divId.red', {}, [h('p','2S后转变')])
            h('div#divId.red', {}, ['文本',h('p','2S后转变')])
            h('div#divId.red', {}, ['文本',h('p','2S后转变'),h('p','2S后转变')])
    

经由过程从上面的这个例子,我们晓得怎样用snabbdom.js来衬着页面了,不过漏了一个重点,就是h函数的第二个参数,模块参数的运用,下面我们革新一下vnode;

vnode = h('div#divId.red', {
    'class': {
        'active': true
    },
    'style': {
        'background': '#fff'
    },
    'on': {
        'click': clickFn
    },
    'dataset': {
        'name': 'liuzj'
    },
    'hook': {
        'init': function() {
            console.log('init')
        },
        'create': function() {
            console.log('create')
        },
        'insert': function() {
            console.log('insert')
        },
        'prepatch': function() {
            console.log('beforePatch')
        },
        'update': function() {
            console.log('update')
        },
        'postpatch': function() {
            console.log('postPatch')
        },
        'destroy': function() {
            console.log('destroy')
        },
        'remove': function(ch, rm) {
            console.log('remove')
            rm();
        }
    }
}, [h('p', {}, '2S后转变')])

function clickFn() {
    console.log('click')
}



vnode = patch(app, vnode);

下面是代码的结果:
《Snabbdom.js(一)》

  • class:这里我们能够理解为动态的类名,sel上的类能够理解为静态的,比方上面class:{active:true}我们能够经由过程掌握这个变量来示意此元素是不是是当前被点击
  • style:内联款式
  • on:绑定的事宜范例

    关于绑定事宜的实践:

  1. 绑定click事宜,不传自定义参数

       var newVnode = h('div', {
       on: {
           'click':clickfn1
       }},'div')
    
       function clickfn1(e,vnode) {
           console.log(e)
           console.log(vnode)
       }
    
  2. 绑定click事宜,传自定义参数

       var newVnode = h('div', {
       on: {
           'click':[clickfn1,'arg1','arg2']
       }},'div')
    
       function clickfn1(val1,val2,e,vnode) {
           console.log(val1)
           console.log(val2)
           console.log(e)
           console.log(vnode)
       }
       
  3. 为click事宜绑定多个回调函数

     var newVnode = h('div', {
       on: {
           'click':[[clickfn1,'arg1','arg2'],[clickfn2,'arg1','arg2']]
       }},'div')
    
       function clickfn1(val1,val2,e,vnode) {
           console.log(val1)
           console.log(val2)
           console.log(e)
           console.log(vnode)
       }
       
       function clickfn2(val1,val2,e,vnode) {
           console.log(val1)
           console.log(val2)
           console.log(e)
           console.log(vnode)
       }
    

在绑定多个回调函数时,源码存在一个题目,回调参数中的event和vnode猎取不到,修改源码即可:

eventlistener.js:

for (var i = 0; i < handler.length; i++) {
    invokeHandler(handler[i]);
}

改成:

for (var i = 0; i < handler.length; i++) {
    invokeHandler(handler[i], vnode, event);
}
  • dataset:data属性
  • hook:钩子函数

    《Snabbdom.js(一)》

这些钩子函数是在模块中运用的: pre, create, update, destroy, remove, post.

这些钩子函数是本身定义在假造dom中运用的: init, create, insert, prepatch, update, postpatch, destroy, remove.

在实践钩子函数的时刻碰到的一些状况:

  1. 假如你的vnode举行patch的时刻sel值不同时,只会触发init create destroy remove insert ,由于这理会将旧的vnode悉数删建立新的vnode 比方:sel:div –> sel:p
  2. 假如你的vnode举行patch的时刻sel值雷同时,只会触发beforePatch update postPatch,由于这里只是在旧的vnode上举行更新
  3. 在运用remove钩子函数的时刻须要注重的是,函数会返回一个rm函数参数,我们须要实行这个函数才能将删除旧节点。

举例说明:

var newVnode = h('div#divId', [h('p', '已转变')])

var vnode = h('div#divId.red', {
    'hook': {
        'remove': function() {
            console.log('remove')
        }
    }
}, [h('p', '2S后转变')])


vnode = patch(app, vnode);

setTimeout(function() {
    patch(vnode, newVnode);
}, 2000)

《Snabbdom.js(一)》

准确运用的要领为:

'remove': function(ch, rm) {
            console.log('remove')
            rm();
        }
  • props/attribute:设置元素本身的属性

    h('div#divId.red', [h('a',{ attrs:{ href:'http://baidu.com'}},'百度')])
    h('div#divId.red', [h('a',{ props:{ href:'http://baidu.com'}},'百度')])
    

    不过关于disabled checked如许的属性最好是用props

    h('div#divId.red', [h('button', {props: {disabled: true}}, '按钮')])
    

3.关于Key值的运用

key值算是一个snabbdom中diff算法的一个中心内容,关于diff算法的中心头脑我会鄙人一篇引见,这一篇主假如讲一下运用。

以我的看法来看,多个雷同元素衬着时,则须要为每一个元素增加key值。

比方

<ul>                   <ul>
    <li>li1</li>            <li>li2</li>
    <li>li2</li>   -->      <li>li3</li>
    <li>li3</li>            <li>li4</li>
</ul>                  </ul>
var vnode = h('ul', [h('li', {
    key: 1
}, 'li1'), h('li', {
    key: 2
}, 'li2'), h('li', {
    key: 3
}, 'li3')])

var newVnode = h('ul', [h('li', {
    key: 2
}, 'li2'), h('li', {
    key: 3
}, 'li3'), h('li', {
    key: 4
}, 'li4')])

固然,在现实事情中,我们一定不会像上面那样写,都是应用轮回举行动态衬着。

var data1 = [{
    name: 'li1'
}, {
    name: 'li2'
}, {
    name: 'li3'
}]

var data2 = [{
    name: 'li2'
}, {
    name: 'li3'
}, {
    name: 'li4'
}]

var vnode = h('ul', data1.map(function(item) {
    return h('li', {
        key: item.name
    }, item.name)
}))

var newVnode = h('ul', data2.map(function(item) {
    return h('li', {
        key: item.name
    }, item.name)
}))

vnode = patch(app, vnode);

setTimeout(function() {
    patch(vnode, newVnode);
}, 2000)

这里须要记着的是:这个key值要唯一,而且须要一一对应
很多人喜好在轮回的数组顶用index来作为key值,严厉意义上来讲如许做是不适当的,key值不仅须要唯一,还须要一一对应(同一个节点旧vnode中和新vnode中的key值要一样),固然假如你运用key值的元素它不存在增删排序的需求,那末index作为key值没有影响。
至于缘由,下一篇我会说一下;

前面说到了ul/li须要运用key,另有就是我现在做的表格衬着,也须要运用key,由于表格会涉及到tbody/tr/td,个中tr和td都邑存在多个,而tr会有增删和排序,td只是值的修改,位置不会发生变化,所以我在现实操纵的过程当中,tr的key值一一对应的,而td的key值则是用index来赋值。

愿望人人看完能有收成,迎接斧正!
《Snabbdom.js(一)》

    原文作者:liuzaijiang
    原文地址: https://segmentfault.com/a/1190000017494569
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞