ref
被用來給元素或子組件注冊引用信息。引用信息將會注冊在父組件的 $refs
對象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實例,例如:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="vue.js"></script> </head> <body> <div id="app"> <h1 ref="info">11</h1> <child ref="child"></child> <p v-for="item in items" ref="item">{{item}}</p> <button @click='show'>Test</button> </div> <script> Vue.component('child',{template:'<h1>I am childComponent</h1>'}) var app = new Vue({ el:'#app', data:{items:[11,12,13]}, methods:{show:function(){console.log(this.$refs)}} //點擊后輸出Vue實例的$refs屬性 }) </script> </body> </html>
渲染如下:
點擊Test后輸出如下:
源碼分析
_init初始化的時候會執行initLifecycle()函數,該函數會初始化當前Vue實例的$refs為一個空對象。
掛載的時候首先會將模板解析成一個AST對象,此時會執行processElement()函數,該函數又會執行processRef去解析ref屬性,如下:
function processRef (el) { //第9359行 解析ref屬性 var ref = getBindingAttr(el, 'ref'); //嘗試獲取ref屬性 if (ref) { //如果存在 el.ref = ref; //保存到el.ref里面 el.refInFor = checkInFor(el); //執行checkInFor檢查是否在v-for循環內,將結果保存到el.refInfor里面 } } function checkInFor (el) { //第9605行 檢測ref屬性是否在v-for里面 var parent = el; //首先將el保存到parent里,這樣v-for和ref就可以作用在同一個元素上 while (parent) { //通過檢測parent的AST對象是否由for來判斷 if (parent.for !== undefined) { return true //如果在v-for內則返回true } parent = parent.parent; } return false //否則返回false }
檢測是否在v-for內會影響最后的保存方式,如果在v-for內則最后保存為數組形式,例如例子里的p標簽,否則就是非數組,對於h1屬性來說,執行到這里后,屬性如下:
最后將AST生成render函數的時候會執行genData$2()函數($2是Vue項目build的時候node自動轉換的,防止同名),genData$2()會判斷是否有ref和refInFor屬性,如果有則保存到data屬性上(就是render屬性對應的參數,這個參數是一個函數,函數的第二個參數),如下:
function genData$2 (el, state) { //第10274行 var data = '{'; // directives first. // directives may mutate the el's other properties before they are generated. var dirs = genDirectives(el, state); if (dirs) { data += dirs + ','; } // key if (el.key) { data += "key:" + (el.key) + ","; } // ref if (el.ref) { //對應ref屬性 data += "ref:" + (el.ref) + ","; } if (el.refInFor) { //如果組件元素有設置了v-for指令 data += "refInFor:true,"; } /*略*/ }
最后等到DOM創建后,會執行ref模塊的create鈎子函數(Vue內部有七個模塊,分別對應屬性、樣式、事件、DOM屬性、樣式、動畫、ref和指令,用於在DOM新增、更新、卸載時執行一些列操作)
ref模塊初始化時會執行registerRef函數,如下:
function registerRef (vnode, isRemoval) { //第5389行 ref的實現函數 vnode:節點對應的VNode,isRemoval:是否移除 var key = vnode.data.ref; if (!isDef(key)) { return } //如果沒有定義ref屬性,則直接返回 var vm = vnode.context; //當前的根Vue實例 var ref = vnode.componentInstance || vnode.elm; //優先獲取vonde的組件實例(對於組件來說),或者el(該Vnode對應的DOM節點,非組件來說) 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) { //當在v-for之內時,則保存為數組形式 if (!Array.isArray(refs[key])) { refs[key] = [ref]; } else if (refs[key].indexOf(ref) < 0) { // $flow-disable-line refs[key].push(ref); } } else { //不是在v-for之內時 refs[key] = ref; //直接保存到refs對應的key屬性上 } } }
ref屬性比較簡單的,可以方便的引用某個DOM節點或子組件實例,在很多地方用得到,比如Elementui里的表單等