ref
被用来给元素或子组件注册引用信息,引用信息将会注册在父组件的 $refs
对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。
$refs
的注册
在函数 initLifecycle
上会往 vm
上设置一个 key
为 $refs
值为一个对象,在src/core/instance/lifecycle.js
文件中:
function initLifecycle (vm) {
....
vm.$refs = {};
....
}
解析
挂载的时候首先会将模板解析成一个AST
对象,此时会执行processElement
函数,该函数又会执行processRef
去解析ref
属性,如下:
function processRef (el) {
var ref = getBindingAttr(el, 'ref');
if (ref) {
//保存到el.ref里面
el.ref = ref;
//执行checkInFor检查是否在v-for循环内,将结果保存到el.refInfor里面
el.refInFor = checkInFor(el);
}
}
//检测ref属性是否在v-for里面
function checkInFor (el) {
//首先将el保存到parent里,这样v-for和ref就可以作用在同一个元素上
var parent = el;
//通过检测parent的AST对象是否由for来判断
while (parent) {
//如果在v-for内则返回true
if (parent.for !== undefined) {
return true
}
parent = parent.parent;
}
return false
}
最后将AST
生成render
函数的时候会执行genData
,会判断是否有ref和refInFor属性,如果有则保存到data属性上;
function genData (el, state) {
var data = '{';
var dirs = genDirectives(el, state);
if (dirs) { data += dirs + ','; }
// key
if (el.key) {
data += "key:" + (el.key) + ",";
}
// ref
if (el.ref) {
data += "ref:" + (el.ref) + ",";
}
if (el.refInFor) {
data += "refInFor:true,";
}
......
}
获取元素上的 ref
值
在 ref
对象中,内置函数 registerRef
是核心方法,在src/core/vdom/modules/ref.js
文件中:
export function registerRef (vnode: VNodeWithData, isRemoval: ?boolean) {
//获取ref
const key = vnode.data.ref
if (!isDef(key)) return
const vm = vnode.context
const ref = vnode.componentInstance || vnode.elm
const 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
}
}
}
1、首先通过 vnode.data 获取 ref 作为 key ,这个 key 是用来为获取到的 refs 示例作为键名,然后从 vnode 上下文 context 获取 vm ,获取 $refs;ref 其实是 DOM 节点或组件实例;
2、isRemoval 为 true 是更新和销毁钩子触发的时候,首先看一下 else 里面的逻辑:ref 和 v-for 一起使用的时候,如果不是数组格式,强制转换一下,外层套一个数组;如果是数组看数组里面是否存在当前这个 ref,如果不存在,push 进去;
3、如果不是和 v-for 一起用:直接设置对象的 key 和 value;
4、如果是更新和销毁钩子触发的时候:会对 key 对应的 refs 删除;
总结
1、ref
本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 ,因为它们还不存在;
2、ref
相对来说比较简单,它内置了 create 、update、destory
生命周期钩子方法,会在不同的钩子函数触发的时候自动触发;在 patch
过程中会在invokeCreateHooks
函数中调用 create
,这也就会触发 ref
内置的 create
钩子函数,然后就会调用 registerRef
方法来注册 ref
;
3、在 update、destory
会把对应的 refs
对象删除掉;