前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Vue原理】Compile - 源码版 之 generate 节点数据拼接

【Vue原理】Compile - 源码版 之 generate 节点数据拼接

作者头像
神仙朱
修改2019-08-22 15:43:33
6990
修改2019-08-22 15:43:33
举报
文章被收录于专栏:Vue源码 & 前端进阶体系

写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧

研究基于 Vue版本 【2.5.17】

如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧

【Vue原理】Compile - 源码版 之 generate 节点数据拼接

上一篇我们讲了不同节点的拼接,这一篇需要详细记录的是 节点数据的拼接

节点数据,包括有 props,attrs,事件等

上一篇我们在 genElement 中看到过,每个节点都需要去拼接节点数据,使用的就是下面源码中的 genData$2 这个方法

代码语言:txt
复制
function genElement() {

    .....处理其他类型的节点
    var data = genData$2(el, state);    

    var children = genChildren(el, state);


    code = `_c('${el.tag}', $ {
        data ? ("," + data) : ''
    }, $ {
        children ? ("," + children) : ''
    })`

}

genData$2

这个函数的源码有点长,但是不用怕,都是处理各种属性的判断,所以内容大约一致,不过里面涉及到具体的方法,会具体看

来吧,先过一遍把

代码语言:txt
复制
function genData$2(el, state) {   



   var data = '{';   



   // 先解析指令

   var dirs = genDirectives(el, state);  



   // 拼接上解析得到的指令字符串
   if(dirs) {
        data += dirs + ',';
   }   



   // 带有 is 绑定的组件,直接使用组件则没有

   if(el.component) {
        data += `tag: ${el.tag} , ` 
   }   



   // 上一篇说过的,dataGenFns 包含处理style,class的函数

   for(var i = 0; i < state.dataGenFns.length; i++) {
        data += state.dataGenFns[i](el);
   }   



   // 全部属性

   if(el.attrs) {
        data += ` attrs:{ ${genProps(el.attrs)) } ,` 
   }  



   // 原生属性

   if(el.props) {
        data += ` domProps:{ ${genProps(el.props)} }, ` 
   }   



    // 事件

   if(el.events) {
        data += genHandlers(el.events, false) + ",";
   } 


   // 原生事件

   if(el.nativeEvents) {
        data += genHandlers(el.nativeEvents, true) + ",";
   }   



   // 没有作用域的 slot

   if(
        el.slotTarget && !el.slotScope
    ) {
        data += ` slot: ${ el.slotTarget } ,` 
   }   



   // 作用域slot

   if(el.scopedSlots) {
        data += genScopedSlots(el.scopedSlots, state) + ",";
   }   



   // 组件使用 v-model

   if(el.model) {

        data += `model:{
            value:${el.model.value},
            callback:${el.model.callback},
            expression:${el.model.expression},
        },`
   }



   data = data.replace(/,$/, '') + '}';   



   return data

}

首先这个方法,最终返回的是一个对象的序列化字符串,比如这样

代码语言:txt
复制
" { a:b , c:d } "

所以头尾都会 加上大括号,然后属性拼接xx:yy 的形式

下面我们就来一个个看对于不同属性的处理


拼接指令

代码语言:txt
复制
function genDirectives(el, state) {    



    var dirs = el.directives;    



    if (!dirs) return

    var res = 'directives:[';    

    var hasRuntime = false;    

    var i, l, dir, needRuntime;    



    for (i = 0, l = dirs.length; i < l; i++) {


        dir = dirs[i];
        needRuntime = true;        



        // 获取到特定的 Vue 指令处理方法

        var gen = state.directives[dir.name];    

    

        // 如果这个函数存在,证明这个指令是内部指令
        if (gen) {
            needRuntime = gen(el, dir);
        }        



        if (needRuntime) {


            hasRuntime = true;

            res += `{
                name: ${dir.name},
                rawName: ${dir.rawName}
                ${
                    dir.value
                    ? ",value:" + dir.value +", expression:" + dir.value    

                    : ''
                }
                ${ dir.arg ? ( ",arg:" + dir.arg ) : '' }
                ${
                    dir.modifiers
                    ? (",modifiers:" + JSON.stringify(dir.modifiers))
                    : ''
                }
            }, `

        }
    }    



    if (hasRuntime) {        

        return res.slice(0, -1) + ']'

    }
}

首先呢,我们要了解这个方法会返回什么字符串,比如

公众号
公众号

就会返回这样的字符串

代码语言:txt
复制
`directives:[{
    name:"test",
    rawName:"v-test:a.b.c",
    value:222,
    expression:"arr",
    arg:"a",
    modifiers:{"b":true,"c":true}
}]`

每一个指令,都会解析成一个对象字符串,然后拼接在字符串数组里面

那么下面就来详细记录几个可能疑惑的点

函数中出现的 state.directives

在上面文章中的 CodegenState 中,我们有写过这个

state.directives 是一个数组,包含了 Vue内部指令的处理函数,如下

v-on,v-bind,v-cloak,v-model ,v-text,v-html

函数中的变量 needRuntime

一个标志位,表示是否需要把指令的数据解析成一个 对象字符串,像这样

代码语言:txt
复制
`{
    name:xxx, rawName:xxx,
    value:xxx, expression:xx,
    arg:xx, modifiers:xx
}`

也就是说,这个指令是否需要被拼接成 render 字符串中

那么什么指令需要,什么指令不需要呢?

自定义指令,都需要被解析,拼接在 render 字符串中

但是 Vue 的内部指令,有的用,有的不用,所以就搞出一个 needRunTime 来进行判断

Vue 的指令,先要获取到特定的处理方法,赋值给 gen

gen 处理完返回 true,则表示需要 拼接上render,返回 false 或者不返回,则表示不需要拼接上

比如,v-model 指令的数据就需要拼接上 render,而 v-text,v-html 则不用

看下面的例子

公众号
公众号

比如上面的模板拼接成下面的字符串,发现 v-html 并没有出现在 directives 那个字符串数组中

代码语言:txt
复制
`_c('div',{    

    directives:[{        

        name:"model",

        rawName:"v-model",        

        value:arr,

        expression:"arr"

    }],    

    domProps:{        

        "innerHTML":_s(<span></span>)

    }
})`

函数中的变量 hasRuntime

一个标志位,表示是否需要把 return 指令字符串

genDirectives 处理的是一个指令数组,当数组为空的时候,并不会有返回值

那么 render 字符串就不会 存在 directive 这一段字符串

如果指令不为空,那么 hasRunTime 设为 true,需要返回字符串

并且在 字符串尾部加上 ] , 这样字符串数组就完整了


拼接组件

这里的解析组件,解析的是带有 is 属性的绑定组件

很简单,就是拼接上一个 tag 的属性就ok 了

看例子

公众号
公众号

原有的标签名,被拼接在 tag 后面

代码语言:txt
复制
` _c("test",{tag:"div"}) `

拼接样式

上篇文章也说过,state.dataGenFns 是一个数组

存放的是两个函数,一个是解析 class ,一个是解析 style 的

这里放下其中的源码,非常的简单

解析 class

代码语言:txt
复制
function genData(el) {    

    var data = '';    

    if (el.staticClass) {

        data += "staticClass:" + el.staticClass + ",";
    }   

    if (el.classBinding) {

        data += "class:" + el.classBinding + ",";
    }    

    return data

}

解析style

代码语言:txt
复制
function genData$1(el) {    

    var data = '';    

    if (el.staticStyle) {

        data += "staticStyle:" + el.staticStyle + ",";
    }    

    if (el.styleBinding) {

        data += "style:(" + el.styleBinding + "),";
    }    

    return data

}

实在是太简单的,就是直接拼接上几个属性而已啦

给例子就好了

公众号
公众号
代码语言:txt
复制
`_c('div',{
    staticClass:"a",
    class:name,
    staticStyle:{"height":"0"},
    style:{width:0}
})
`

拼接属性

属性的拼接只有一个函数,内容也十分简单

代码语言:txt
复制
function genProps(props) {    



    var res = '';   



    for (var i = 0; i < props.length; i++) {        

        var prop = props[i];


        res += prop.name + ":" + 
               prop.value + ",";
    }    



    return res.slice(0, -1)

}

你可以看到,虽然只有一个方法,但是在 genData$2 中,拼接的结果会有两种

拼接到 el.attr

公众号
公众号

拼接到 el.props

公众号
公众号

为什么会拼接到不同的地方?

因为看的是你属性 放的位置

如果你的属性位置是 标签上,那么就会拼接到 attr 中

如果你的属性位置是在 dom 上,那么就被拼接到 domProps 中

举个例子

比如下面的模板,bbb 就是放在 标签上,aaa 就是放在 DOM 上

公众号
公众号

拼接的结果就是

代码语言:txt
复制
` _c('div',{
    attrs:{"bbb":"bbb"},
    domProps:{"aaa":11}
}) `

页面标签看不到 aaa

公众号
公众号

可以在 dom 属性中找到 aaa

公众号
公众号

拼接事件

事件的拼接,内容很多,打算放在另一篇文章详细记录

事件拼接还分为两种,原生事件和 自定义事件,只是拼接为不同字符串而已,但是处理方法一样

方法中涉及到各种 修饰符,哈哈,想知道到底为什么能写出这么方便的 api 呢哈哈

绑定按键,阻止默认事件,直接这么写就行了

代码语言:txt
复制
@keyup.enter.prevent="xxx"

欢迎观看下篇文章


拼接普通Slot

就是直接拼接上 slot 这个属性

公众号
公众号
代码语言:txt
复制
` _c('test',[_c('span',{
        attrs:{"slot":"name"},
        slot:"name"
    })]
) `

如果组件有slot,没有 slot 这个属性,那么就不会拼接上slot,后面会直接给个默认名字 “default”


拼接作用域Slot

代码语言:txt
复制
function genScopedSlots(slots, state) {   



    return ` 
        scopedSlots:_u([${            

            Object.keys(slots).map(key =>{                

                return genScopedSlot(key, slots[key], state)
            })
            .join(',')
        }])
    `

}



function genScopedSlot(key, el, state) {  



    var fn = `
        function(${el.slotScope}){
            return ${
                el.tag === 'template' 
                ? genChildren(el, state)
                : genElement(el, state)
            }
        }
    `

    return `{ key:${key} , fn: ${fn} }` 

}

这个处理作用域 slot 的函数看起来好像有一点复杂,但是其实就是纸老虎

不怕,先看一个实例

公众号
公众号

拼接成字符串,是这样的

代码语言:txt
复制
`  
  _c('div',{
     scopedSlots:_u([{
         key:"heder",
         fn:function(arr){return _c('div')}
     }])
  })
`

这个函数遍历的是 el.scopeSlots 这个数组,或许你不知道这个数组是什么内容?

同样给个例子,这里有两个 slot

公众号
公众号

经过 parse 解析之后成一个 ast,是这样的

代码语言:txt
复制
{    

    tag:"test",    

    scopedSlots:[{        

        slotScope: "arr"

        slotTarget: ""a""
        tag: "div"
    },{        

        slotScope: "arr"

        slotTarget: ""b""
        tag: "div"
    }]
}

没错,遍历的就是上面对象里面的 scopedSlots 数组,数组中的每一项都是一个单独的 slot

然后会使用 genScopeSlot 去单独处理一下,上面有放出源码

处理完之后,形成一个新的数组,genScopeSlot 也没什么好说的

拼接分类型,需要判断 slot 位置的标签是不是 template

如果是template,那么他的真实slot 是 template 的子节点,直接获取他的子节点

如果不是template,那么本身就是真实的slot

因为 template 是Vue 自带的一个 模板节点,是不存在的


拼接组件VModel

没错,这里的 model,只是属于 组件的 v-model

代码语言:txt
复制
if (el.model) {

    data += `model: {
        value: $ { el.model.value },
        callback: $ { el.model.callback },
        expression: $ { el.model.expression },
    }, `

}

官网说了这个是怎么用的

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件

也就是说,起始就是给组件传了一个 value,绑定了一个事件 input

也没有什么好讲的,记录下组件的 v-model 是这么拼接就好了

例子

公众号
公众号

经过 parse 解析,得到 ast

代码语言:txt
复制
{    

    tag: "test",    

    model:{        

        callback: "function ($$v) {num=$$v}"

        expression: ""num""
        value: "num"
    }
}

拼接成字样变成字符串了

代码语言:txt
复制
` 
  _c('test',{
      model:{
          value:num,
          callback:function ($$v) {num=$$v},
          expression:"num"
      }
  })
`

举个栗子

属性拼接呢,我们就讲完了,最后我们来看一个例子吧

下面这个模板,我们把它拼接起来

公众号
公众号

解析成下面这个 render 字符串,看懂了,你就掌握了 generate 的内容了

以后你就可以去看别人用Vue 写的打包后的代码了

甚至,你可以手动还原他,如果你闲得很,你可以自己写个方法,传入render 字符串,自动还原成 template 模板

代码语言:txt
复制
` _c('div', {
    attrs: {
        "b": "2"
    },
    domProps: {
        "a": 11
    }
},[
    _c('test', {

       scopedSlots: _u([{
          key: "a",
          fn: function(arr) {
              return _c('strong')
          }
      }]),

      model: {
          value: (num),
          callback: function($$v) {
              num = $$v
          },
          expression: "num"
      }

    },[_c('span')])
]) `

最后

鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵,如果有任何描述不当的地方,欢迎后台联系本人,领取红包

公众号
公众号
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 神仙朱 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • genData$2
  • 拼接指令
    • 函数中出现的 state.directives
      • 函数中的变量 needRuntime
        • 那么什么指令需要,什么指令不需要呢?
          • 函数中的变量 hasRuntime
          • 拼接组件
          • 拼接样式
            • 解析 class
              • 解析style
              • 拼接属性
                • 拼接到 el.attr
                  • 拼接到 el.props
                  • 拼接事件
                  • 拼接普通Slot
                  • 拼接作用域Slot
                  • 拼接组件VModel
                    • 例子
                    • 举个栗子
                    • 最后
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档