一套代码小程序&Web&Native运行的探索03——处理模板及属性

接上文: 一套代码小程序&Web&Native运行的探索02 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/mvvm 我们在研究如果小程序在多端运行的时候,基本在前端框架这块陷入了困境,因为市面上没有框架可以直接拿来用,而Vue的相识度比较高,而且口碑很好,我们便接着这个机会同步学习Vue也解决我们的问题,我们看看这个系列结束后,会不会离目标进一点,后续如果实现后会重新整理系列文章...... 参考: https://github.com/fastCreator/MVVM(极度参考,十分感谢该作者,直接看Vue会比较吃力的,但是看完这个作者的代码便会轻易很多,可惜这个作者没有对应博客说明,不然就爽了) https://www.tangshuang.net/3756.html https://www.cnblogs.com/kidney/p/8018226.html https://github.com/livoras/blog/issues/13 上文中我们借助HTMLParser这种高级神器,终于将文本中的表达式替换了出来,这里单纯说文本这里也有以下问题:这段是不支持js代码的,+-、三元代码都不支持,所以以上都只是帮助我们理解,还是之前那句话,越是单纯的代码,越是考虑少的代码,可能越是能理解实现,但是后续仍然需要补足,我们这里还是要跟Vue对齐,这样做有个好处,当你不知道怎么做的时候,可以看看Vue的实现,当你思考这么做合不合适的时候,也可以参考Vue,那可是经过烈火淬炼的,值得深度学习,我们今天的任务比较简单便是完整的处理完style、属性以及表达式处理,这里我们直接在fastCreator这个作者下的源码开始学习,还有种学习源码的方法就是抄三次...... 我们学习的过程,先将代码写到一起方便理解,后续再慢慢拆分,首先是MVVM类,我们新建libs文件夹,先新建两个js文件,一个html-parser一个index(框架入口文件) 复制代码 libs --index.js --html-parser.js index.html 复制代码 index html-parser 这个时候我们的index代码量便下来了: 复制代码 1 2 3 4 起步 5 6 7 8
9 10
11 12 34 35 复制代码 我们现在来更改index.js入口文件的代码,这里特别说一下其中的$mount方法,他试试是要做一个这样的事情: 复制代码 //模板字符串
{{message}}
复制代码 复制代码 //render函数 function anonymous() { with(this){return _h('div',{attrs:{"id":"app"}},["\n "+_s(message)+"\n"])} } 复制代码 将模板转换为一个函数render放到参数上,这里我们先简单实现,后续深入后我们重新翻下这个函数,修改后我们的index.js变成了这个样子: index.js 这里仅仅是到输出vnode这步,接下来是将vnode转换为函数render,在写这段代码之前我们来说一说Vue中的render参数,事实上,我们new Vue的时候可以直接传递render参数: 复制代码 1 new Vue({ 2 render: function () { 3 return this._h('div', { 4 attrs:{ 5 a: 'aaa' 6 } 7 }, [ 8 this._h('div') 9 ]) 10 } 11 }) 复制代码 他对应的这段代码: 复制代码 1 new Vue({ 2 template: '
Hello World!
' 3 }) 复制代码 真实代码过程中的过程,以及我们上面代码的过程是,template 字符串 => 虚拟DOM对象 ast => 根据ast生成render函数......,这里又涉及到了另一个需要引用的工具库snabbdom snabbdom-render https://github.com/snabbdom/snabbdom,Vue2.0底层借鉴了snabdom,我们这里先重点介绍他的h函数,h(help帮助创建vnode)函数可以让我们轻松创建vnode,这里再对Virtual DOM做一个说明,这段话是我看到觉得很好的解释的话(https://github.com/livoras/blog/issues/13): 我们一段js对象可以很容易的翻译为一段HTML代码: 复制代码 1 var element = { 2 tagName: 'ul', // 节点标签名 3 props: { // DOM的属性,用一个对象存储键值对 4 id: 'list' 5 }, 6 children: [ // 该节点的子节点 7 {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]}, 8 {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]}, 9 {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]}, 10 ] 11 } 复制代码 复制代码 1
    2
  • Item 1
  • 3
  • Item 2
  • 4
  • Item 3
  • 5
复制代码 同样的,我们一段HTML代码其实属性、参数是很有限的,也十分轻易的能转换成一个js对象,我们如果使用dom操作改变了我们的html结构,事实上会形成一个新的js对象,这个时候我们将渲染后形成的js对象和渲染前形成的js对象进行对比,便可以清晰知道这次变化的差异部分,然后拿着差异部分的js对象(每个js对象都会映射到一个真实的dom对象)做更新即可,关于Virtual DOM文章作者对此做了一个总结: ① 用js对象表示DOM树结构,然后用这个js对象树结构生成一个真正的DOM树(document.create***操作),插入文档中(这个时候会形成render tree,看得到了) ② 当状态变化时(数据变化时),重新构造一颗新的对象树,和之前的作对比,记录差异部分 ③ 将差异部分的数据更新到视图上,更新结束 他这里描述的比较简单,事实上我们根据昨天的学习,可以知道框架事实上是劫持了没个数据对象,所以每个数据对象做了改变,会影响到哪些DOM结构是有记录的,这块我们后面章节再说,我们其实今天主要的目的还是处理文本和属性生成,却不想提前接触虚拟DOM了...... 其实我们之前的js对象element就已经可以代表一个虚拟dom了,之所以引入snabbddom应该是后面要处理diff部分,所以我们乖乖的学吧,首先我们定义一个节点的类: 复制代码 1 class Element { 2 constructor(tagName, props, children) { 3 this.tagName = tagName; 4 this.props = props; 5 this.children = children; 6 } 7 } 复制代码 上面的dom结构便可以变成这样了: 复制代码 1 new Element('ul', {id: 'list'}, [ 2 new Element('li', {class: 'item'}, ['Item 1']), 3 new Element('li', {class: 'item'}, ['Item 2']), 4 new Element('li', {class: 'item'}, ['Item 3']) 5 ]) 复制代码 似乎代码有点不好看,于是封装下实例化操作: 复制代码 1 class Element { 2 constructor(tagName, props, children) { 3 this.tagName = tagName; 4 this.props = props; 5 this.children = children; 6 } 7 } 8 9 function el(tagName, props, children) { 10 return new Element(tagName, props, children) 11 } 12 13 el('ul', {id: 'list'}, [ 14 el('li', {class: 'item'}, ['Item 1']), 15 el('li', {class: 'item'}, ['Item 2']), 16 el('li', {class: 'item'}, ['Item 3']) 17 ]) 复制代码 然后就是根据这个js对象生成真正的DOM结构,也就是上面的html字符串: 复制代码 1 2 3 4 起步 5 6 7 8 59 60 61 复制代码 饶了这么大一圈子,我们再回头看这段代码: 复制代码 1 new Vue({ 2 render: function () { 3 return this._h('div', { 4 attrs:{ 5 a: 'aaa' 6 } 7 }, [ 8 this._h('div') 9 ]) 10 } 11 }) 复制代码 这个时候,我们对这个_h干了什么,可能便有比较清晰的认识了,于是我们回到我们之前的代码,暂时跳出snabbdom 解析模板 在render中,我们有这么一段代码: 复制代码 1 //没有指令时运行,或者指令解析完毕 2 function nodir(el) { 3 let code 4 //设置属性 等值 5 const data = genData(el); 6 //转换子节点 7 const children = genChildren(el, true); 8 code = `_h('${el.tag}'${ 9 data ? `,${data}` : '' // data 10 }${ 11 children ? `,${children}` : '' // children 12 })` 13 return code 14 } 复制代码 事实上这个跟上面那坨代码完成的工作差不多(同样的遍历加递归),只不过他这里还有更多的目的,比如这段代码最终会生成这样的: _h('div',{},[_h('div',{},["\n "+_s(name)]),_h('input',{}),_h('br',{})]) 这段代码会被包装成一个模板类,等待被实例化,显然到这里还没进入我们的模板解析过程,因为里面出现了_s(name),我们如果加一个span的话会变成这样: 复制代码 1
2
3 {{name}}
4 {{age+1}} 5 6
7
复制代码 _h('div',{},[_h('div',{},["\n "+_s(name)]),_h('span',{},[_s(age+1)]),_h('input',{}),_h('br',{})]) 真实运行的时候这段代码是这个样子的: 这段代码很纯粹,不包含属性和class,我们只需要处理文本内容替换即可,今天的任务比较简单,所以接下来的流程后便可以得出第一阶段代码: 复制代码 1 2 3 4 起步 5 6 7 8
9 10
11 12 36 37 复制代码 libs/index.js 之前我们图简单,一直没有解决属性问题,现在我们在模板里面加入一些属性: 复制代码 1
2
3 {{name}}
4 {{age+1}} 5 6
7
复制代码 情况就变得有所不同了,这里多加一句: 复制代码 1 setElAttrs(el, delimiters) 2 //==> 3 function setElAttrs(el, delimiters) { 4 var s = delimiters[0], e = delimiters[1]; 5 var reg = new RegExp(`^${s}(\.+\)${e}$`); 6 var attrs = el.attrsMap; 7 for (let key in attrs) { 8 let value = attrs[key]; 9 var match = value.match(reg) 10 if (match) { 11 value = match[1]; 12 if (isAttr(key)) { 13 el.props[key] = '_s('+value+')'; 14 } else { 15 el.attrs[key] = value; 16 } 17 } else { 18 if (isAttr(key)) { 19 el.props[key] = "'" + value + "'"; 20 } else { 21 el.attrs[key] = "'" + value + "'"; 22 } 23 } 24 25 } 26 } 复制代码 这段代码会处理所有的属性,如果是属性中包含“{{}}”关键词,便会替换,不是我们的属性便放到attrs中,是的就放到props中,这里暂时不太能区分为什么要分为attrs何props,后续我们这边给出代码,于是我们的index.js变成了这个样子: libs/index.js 复制代码 _h('div',{attrs:{"data-name":name,"data-flag":'start',"ontap":'clickHandler'},props:{"class":'c-row search-line'}}, [_h('div',{props:{"class":'c-span9 js-start search-line-txt'}}, ["\n "+_s(name)]),_h('span',{}, [_s(age+1)]),_h('input',{props:{"type":'text',"value":_s(age)}}),_h('br',{})]) 复制代码 复制代码 1
2
3
4 叶小钗
5 31 6 7
8
9
复制代码 然后我们来处理class以及style,他们是需要特殊处理的: 复制代码
{{name}}
{{age+1}}
复制代码 libs/index.js 生成了如下代码: 复制代码 1
2
3 叶小钗
4 31 5 6
7
复制代码 虽然这段代码能运行,无论如何我们的属性和class也展示出来了,但是问题却不少: ① 这段代码仅仅就是为了运行,或者说帮助我们理解 ② libs/index.js代码已经超过了500行,维护起来有点困难了,连我自己都有时候找不到东西,所以我们该分拆文件了 于是,我们暂且忍受这段说明性(演示性)代码,将之进行文件分拆 文件分拆 文件拆分后代码顺便传到了github上:https://github.com/yexiaochai/wxdemo/tree/master/mvvm 这里简单的解释下各个文件是干撒的: 复制代码 1 ./libs 2 ..../codegen.js 代码生成器,传入一个ast(js树对象),转换为render函数 3 ..../helps.js 处理vnode的相关工具函数,比如处理属性节点,里面的生成函数感觉该放到utils中 4 ..../html-parser.js 第三方库,HTML解析神器,帮助生成js dom树对象 5 ..../instance.js 初始化mvvm实例工具类 6 ..../mvvm.js 入口函数 7 ..../parser.js 模板解析生成render函数,核心 8 ..../text-parser.js 工具类,将{{}}做替换生成字符串 9 ..../utils.js 工具库 10 ..../vnode.js 虚拟树库,暂时自己写的,后续要换成snabbdom 11 ./index.html 入口文件 复制代码https://www.cnblogs.com/yexiaochai/p/9699698.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信