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中指令的实现原理的主要内容,如果未能解决你的问题,请参考以下文章