Vue $refs 原理
这篇文章分析Vue实例怎么通过$refs 访问到dom元素的。通过上一篇的例子来进行分析:<html><head><style type="text/css"></style></head><body><script src="./vue.js"></script>...
这篇文章分析Vue实例怎么通过$refs 访问到dom元素的。通过上一篇的例子来进行分析:
<html>
<head>
<style type="text/css">
</style>
</head>
<body>
<script src="./vue.js"></script>
<div id="app">
<div id="div1Id" ref="div1Ref" style="margin-bottom: 20px">div 1 test</div>
<button @click="addBorder()">add border for div1</button>
</div>
<script>
const app = new Vue({
el: "#app",
methods : {
addBorder : function(){
//document.getElementById("div1Id").style.border = "5px solid red";
console.log("div1 is: " + this.$refs.div1Ref.outerHTML);
this.$refs.div1Ref.style.border = "5px solid blue";
}
}
});
</script>
</body>
</html>
<div id="app">
<div id="div1Id" ref="div1Ref" style="margin-bottom: 20px">div 1 test</div>
<button @click="addBorder()">add border for div1</button>
</div>
通过Vue模板编译系统生成的render函数如下:
with(this){return _c('div',{attrs:{"id":"app"}},[_c('div',{ref:"div1Ref",staticStyle:{"margin-bottom":"20px"},attrs:{"id":"div1Id"}},[_v("div 1 test")]),_v(" "),_c('button',{on:{"click":function($event){return addBorder()}}},[_v("add border for div1")])])}
我们看到ref = "div1Ref" 被编译到了render函数中的data参数中,我们在createElement 加个打印看看这个data:
function createElement (
context,
tag,
data,
children,
normalizationType,
alwaysNormalize
) {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children;
children = data;
data = undefined;
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE;
}
console.log("liubbc data is: " + JSON.stringify(data));
return _createElement(context, tag, data, children, normalizationType)
}
打印如下:
liubbc data is: {"ref":"div1Ref","staticStyle":{"margin-bottom":"20px"},"attrs":{"id":"div1Id"}}
接下来就是创建VNode,并把data参数赋值给VNode,代码如下:
function _createElement (
context,
tag,
data,
children,
normalizationType
) {
//some code
vnode = new VNode(
tag, data, children,
undefined, undefined, context
);
//some code
}
var VNode = function VNode (
tag,
data,
children,
text,
elm,
context,
componentOptions,
asyncFactory
) {
this.tag = tag;
this.data = data;
//some code
}
我们省略了一些代码,但还是能清晰的看到给VNode 的data属性赋值过程。对ref = "div1Ref"的处理就到这里了,那么怎么通过this.$refs.div1Ref就能访问到div呢?我们在全局vue.js中搜索$refs,发现只有两处:
function initLifecycle (vm) {
var options = vm.$options;
// locate first non-abstract parent
var parent = options.parent;
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent;
}
parent.$children.push(vm);
}
vm.$parent = parent;
vm.$root = parent ? parent.$root : vm;
vm.$children = [];
vm.$refs = {};
vm._watcher = null;
vm._inactive = null;
vm._directInactive = false;
vm._isMounted = false;
vm._isDestroyed = false;
vm._isBeingDestroyed = false;
}
function registerRef (vnode, isRemoval) {
var key = vnode.data.ref;
console.log("liubbc registerRef key: " + key);
if (!isDef(key)) { return }
var vm = vnode.context;
var ref = vnode.componentInstance || vnode.elm;
var refs = vm.$refs;
if (isRemoval) {
if (Array.isArray(refs[key])) {
remove(refs[key], ref);
} else if (refs[key] === ref) {
refs[key] = undefined;
}
} else {
if (vnode.data.refInFor) {
if (!Array.isArray(refs[key])) {
refs[key] = [ref];
} else if (refs[key].indexOf(ref) < 0) {
// $flow-disable-line
refs[key].push(ref);
}
} else {
refs[key] = ref;
}
}
}
从上面代码看到,第一处initLifecycle函数中 vm.$refs = {}; 这行代码就让vue实例拥有了一个$refs对象。从第二处registerRef函数名中就看到这是注册$refs的地方。我们先分析一下这个函数。其实这个函数还是蛮简单的,我们看一下几处关键代码:
var key = vnode.data.ref; //之前分析过,vnode的data为{"ref":"div1Ref","staticStyle":
//{"margin-bottom":"20px"},"attrs":{"id":"div1Id"}}
var ref = vnode.componentInstance || vnode.elm; //如果是组件实例,ref则指向组件实例,否则指向
//elm, elm就是div dom元素
var refs = vm.$refs; //refs 指向vue实例$refs对象
refs[key] = ref; // vue实例$refs对象的div1Ref 属性赋值为div dom元素
到这里基本上我们也就明白了this.$refs.div1Ref 能取得div dom元素的过程了,但还有一点需要澄清,就是谁调用的registerRef函数。还是在vue.js中全局搜索registerRef,又加了点打印,发现在这个地方调用了这个函数:
var ref = {
create: function create (_, vnode) {
console.log("liubbc ref create");
registerRef(vnode);
},
update: function update (oldVnode, vnode) {
console.log("liubbc ref update");
if (oldVnode.data.ref !== vnode.data.ref) {
registerRef(oldVnode, true);
registerRef(vnode);
}
},
destroy: function destroy (vnode) {
console.log("liubbc ref destroy");
registerRef(vnode, true);
}
};
从这个ref对象中的create,update,destroy 属性来看,都调用了registerRef函数。凭经验应该从create函数中来找,那么谁又调用了create函数呢?
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, vnode); //在这里调用了create函数
}
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) {
i.create(emptyNode, vnode);
}
if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
}
}
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
//some code
var data = vnode.data; //vnode.data 之前分析过里面有ref属性
//some code
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode); //创建了真实的div dom
//some code
{
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue); //这里注册create hooks
}
insert(parentElm, vnode.elm, refElm);
}
对关键代码进行了注释。在创建真实dom过程中做了invoke create hooks操作。在invokeCreateHooks函数中的cbs又是什么呢?
var hooks = ['create', 'activate', 'update', 'remove', 'destroy']; //定义了这么多hooks
function createPatchFunction (backend) {
var i, j;
var cbs = {}; //invokeCreateHooks 访问了cbs,所以形成了闭包
var modules = backend.modules;
var nodeOps = backend.nodeOps;
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []; // 在这里注册了create hook
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
cbs[hooks[i]].push(modules[j][hooks[i]]); //往create hook 里面push 钩子函数
}
}
}
//some code
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, vnode); // 遍历调用cbs中的create 钩子
}
}
}
上面代码就是注册create hooks过程,我们看看往create hook 里面push的钩子函数有哪些?是否有var ref中的create钩子函数?
var baseModules = [
ref, //这里就是ref 对象
directives
];
var platformModules = [
attrs,
klass,
events,
domProps,
style,
transition
];
/* */
// the directive module should be applied last, after all
// built-in modules have been applied.
var modules = platformModules.concat(baseModules);
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules }); //modules中
//含有ref对象
从上面我们看到其实在加载vue.js的时候都把var ref中的create 钩子函数注册进cbs了。在创建真实dom过程中会检测vnode中的data属性是否为空,如果不为空就invokeCreateHooks 调用create 钩子函数,在create钩子函数中给$refs对象创建名为data中ref属性值的属性div1Ref,并使div1Ref属性值指向真实的div dom元素。
不知道写清楚没有,如哪里没看明白,请留言。
更多推荐
所有评论(0)