vue中指令的实现原理

Posted 前端精髓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue中指令的实现原理相关的知识,希望对你有一定的参考价值。

首先生成的 ast 会加上一些属性,每个 element 元素可以看作是一个 ast 对象,整颗 DOM 树可以看作是包含依赖关系的 ast 对象。

v-if 指令

源码在 processIf(element) 函数里面处理

// <span v-if="msg">6</span>
// (msg)?_c('span',[_v("6")]):_e()
let ast = [
  {
    type: 1,
    tag: 'span',
    attrsList: [],
    attrsMap: { 'v-if': 'msg' },
    rawAttrsMap: {},
    parent: {
      type: 1,
      tag: 'div',
      attrsList: [],
      attrsMap: {},
      rawAttrsMap: {},
      parent: undefined,
      children: [],
      plain: true,
      static: false,
      staticRoot: false
    },
    children: [],
    if: 'msg',
    ifConditions: [ { exp: 'msg', block: el } ],
    plain: true,
    static: false,
    staticRoot: false,
    pre: undefined,
    ifProcessed: true
  }
]

先 processIf 再 genIf,生成 render:

function genIf (
  el,
  state,
  altGen,
  altEmpty
) {
  el.ifProcessed = true; // avoid recursion
  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}

function genIfConditions (
  conditions, // [ { exp: 'msg', block: el } ]
  state,
  altGen, // 无
  altEmpty // 无
) {
  if (!conditions.length) {
    return altEmpty || '_e()'
  }

  var condition = conditions.shift();
  if (condition.exp) {
    // 主要看这里,通过三元运算符?和:拼接字符串
    return ("(" + (condition.exp) + ")?" + (genElement(el, state)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
  } else {
    return ("" + (genTernaryExp(condition.block)))
  }
}

v-for 指令

源码在 processFor(element) 函数里面处理

// _l((list), function(item){return _c('span',[_v("6")])})
// <span v-for="item in list">6</span>
let ast = [
  {
    type: 1,
    tag: 'span',
    attrsList: [],
    attrsMap: { 'v-for': 'item in list' },
    rawAttrsMap: {},
    parent: {
      type: 1,
      tag: 'div',
      attrsList: [],
      attrsMap: {},
      rawAttrsMap: {},
      parent: undefined,
      children: [],
      plain: true,
      static: false,
      staticRoot: false
    },
    children: [],
    for: 'list',
    alias: 'item',
    plain: true,
    static: false,
    staticRoot: false,
    pre: undefined,
    forProcessed: true
  }
]

我们简单想象一下for指令需要包装成一个函数,方便之后的循环遍历,比如下面这样:

function _l(list, callback) { list.forEach(item => callback(item))  }

具体的生成过程:

function genFor (
  el,
  state,
  altGen, // 无
  altHelper // 无
) {
  var exp = el.for;
  var alias = el.alias;
  var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
  var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : '';
  // 主要看这里,包装成一个function函数
  el.forProcessed = true; // avoid recursion
  return ('_l') + "((" + exp + ")," +
    "function(" + alias + iterator1 + iterator2 + "){" +
      "return " + ((genElement)(el, state)) +
    '})'
}

v-once 指令

源码在 processOnce(element) 函数里面处理

// <div>11<span v-once="msg">6</span></div>
// render: `with(this){return _c('div',[_v("11"),_m(0)])}`,
// staticRenderFns: [ `with(this){return _c('span',[_v("6")])}` ],
let ast = [
  {
    type: 1,
    tag: 'span',
    attrsList: [],
    attrsMap: { 'v-once': '' },
    rawAttrsMap: {},
    parent: {
      type: 1,
      tag: 'div',
      attrsList: [],
      attrsMap: {},
      rawAttrsMap: {},
      parent: undefined,
      children: [],
      plain: true,
      static: false,
      staticRoot: false
    },
    children: [],
    once: true,
    plain: true,
    static: false,
    staticInFor: false,
    staticRoot: false,
    pre: undefined,
    onceProcessed: true,
    staticProcessed: true
  }
]

v-once只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

function genOnce (el, state) {
  el.onceProcessed = true;
  return genStatic(el, state)
}

function genStatic (el, state) {
  el.staticProcessed = true;
  var originalPreState = state.pre;
  state.staticRenderFns.push(("with(this){return " + (genElement(el, state)) + "}"));
  state.pre = originalPreState;
  return ("_m(" + (state.staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")")
}

生成 render 函数的目的就是为了当绑定的变量值发生改变的时候,可以重新执行 render 函数,在生成真是的 DOM,其中_c 和 _v 等等这些辅助函数就是处理真实 DOM 的函数,关于 render 函数生成 DOM 我们之后在分析。

插槽

const compiler = require('vue-template-compiler')
let str = `<div><my-button><template v-slot:header="slotProps">Header content</template></my-button></div>`

console.log(compiler.compile(str).ast.children[0].scopedSlots)


// v-slot:header render: `with(this){return _c('div',[_c('my-button',{scopedSlots:_u([{key:"header",fn:function(){return [_v("Header content")]},proxy:true}])})],1)}`,
// v-slot:header="slotProps" 作用域插槽 render: `with(this){return _c('div',[_c('my-button',{scopedSlots:_u([{key:"header",fn:function(slotProps){return [_v("Header content")]}}])})],1)}`,

let ast = [
  {
    type: 1,
    tag: 'my-button',
    attrsList: [],
    attrsMap: {},
    rawAttrsMap: {},
    parent: {
      type: 1,
      tag: 'div',
      attrsList: [],
      attrsMap: {},
      rawAttrsMap: {},
      parent: undefined,
      children: [],
      plain: true,
      static: false,
      staticRoot: false
    },
    children: [],
    scopedSlots: { 
      '"header"': {
        type: 1,
        tag: 'template',
        attrsList: [],
        attrsMap: { 'v-slot:header': 'slotProps' },
        rawAttrsMap: {},
        parent: {
          type: 1,
          tag: 'my-button',
          attrsList: [],
          attrsMap: {},
          rawAttrsMap: {},
          parent: [],
          children: [],
          scopedSlots: [],
          plain: false,
          static: false,
          staticRoot: false,
          pre: undefined
        },
        children: [],
        plain: false,
        slotScope: 'slotProps',
        slotTarget: '"header"',
        slotTargetDynamic: false
      }
    },
    plain: false,
    static: false,
    staticRoot: false,
    pre: undefined
  }
]

插槽编译之后也是一个函数包裹的

function(){return [_v("Header content")]}

如果是作用域插槽就把参数带上

function(slotProps){return [_v("Header content")]

事件绑定

const compiler = require('vue-template-compiler')
let str = `<div><span @click="handleClick"></span></div>`

console.log(compiler.compile(str))
{
  ast: {
    type: 1,
    tag: 'div',
    attrsList: [],
    attrsMap: {},
    rawAttrsMap: {},
    parent: undefined,
    children: [],
    plain: true,
    static: false,
    staticRoot: false
  },
  render: `with(this){return _c('div',[_c('span',{on:{"click":handleClick}})])}`,
  staticRenderFns: [],
  errors: [],
  tips: []
}

经过多次的练习,我们发现 _c 就是我们的 h 函数

render (h) {
  throw new Error('oops')
}

以上是关于vue中指令的实现原理的主要内容,如果未能解决你的问题,请参考以下文章

实模式与保护模式详解三:寻址方式

详解Dockerfile原理镜像容器命令总结

linux下进度条的简单实现

Vue路由的原理及模式

Vue路由的原理及模式

计算机组成原理--MIPS指令的表示和逻辑操作