Vue.js 源碼分析(十) 基礎篇 ref屬性詳解

ref 被用來給元素或子組件註冊引用信息。引用信息將會註冊在父組件的 $refs 對象上。若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;若是用在子組件上,引用就指向組件實例,例如:html

<!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>

渲染以下:vue

點擊Test後輸出以下:node

 

源碼分析數組


 _init初始化的時候會執行initLifecycle()函數,該函數會初始化當前Vue實例的$refs爲一個空對象。app

掛載的時候首先會將模板解析成一個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新增、更新、卸載時執行一些列操做)ui

ref模塊初始化時會執行registerRef函數,以下:this

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裏的表單等

相關文章
相關標籤/搜索