你不知道的Vuejs - 深入淺出響應式系統

rcoq9270 6年前發布 | 22K 次閱讀 Vue Vue.js開發

雖然說是Vuejs實踐,但是有些重要的理論還是必不可少的,本文將簡單的帶你了解 Vuejs的響應式原理 。

Vue 最獨特的特性之一,是其非侵入性的響應式系統。數據模型僅僅是普通的 Javascript 對象。而當你修改它們時,視圖會進行更新。這使得狀態管理非常簡單直接,不過理解其工作原理同樣非常重要,這樣你可以回避一些常見的問題。

原理圖剖析

仔細閱讀這張官方原理圖,大概可以剖析為以下幾個步驟:

  • 編譯組件:對特殊標記的部分(比如雙大括號部分)進行替換為相應的數據值。
  • 收集依賴:對于編譯階段依賴的數據進行監聽(這個都是通過 watcher 對象實現的)
  • 通知更新:當步驟2中監聽的數據發生變化時,會通知 watcher 進行重新計算,觸發關聯視圖更新。

可以簡單理解為一個發布訂閱系統,當然這里的過程介紹比較通俗,單純是為了理解而解釋的,實際流程其實還是很復雜的。如果想結合源碼深入了解的,建議去閱讀這篇文章:

Vue 源碼解析:深入響應式原理

關于Vuejs模板

上一篇中提到,Vue實例在初始化的時候會將目標節點 div#app 內容進行替換,是在定義了 template 屬性或者 render 函數的前提下。否則會對把 div#app 內容當做模板進行編譯輸出。一般情況下,很少使用 template 屬性來定義模板,因為實際開發過程中,我們的模板還是很復雜的,單純通過 template 屬性定義,我們的代碼會顯得非常臃腫,不便于閱讀。所以,我們通常使用的是將模板內容寫在過載目標元素的內部,稍后將采用這種方法做代碼演示。

當然對于復雜項目,更多的是使用 單文件組件 (這將在以后專題文章中介紹)。

百聞不如一見

還記得上一篇中講述的如何實現一個簡單的 Vue 應用,并通過不同的 API 實現了目標元素的渲染吧。不記得也不要緊,這里還是從最基本的代碼開始。下面我們會動態的實現一個列表的渲染,并通過修改數據,來觸發視圖的更新,以此來感受下響應式系統的快感。先來看一段代碼:

Demo1: data

<divid="app">
  <buttonv-on:click="addItem">添加</button>
  <ul>
    <liv-for="(item, index) in list"v-bind:key="index">
      <av-bind:href="item.url">{{ item.name }}</a>
    </li>
  </ul>
</div>
var app = new Vue({
  el: "#app",
  data () {
    return {
      count: 1,
      list: [
        {
          name: 'Vuejs官網',
          url: 'https://cn.vuejs.org'
        },
        {
          name: 'Github',
          url: 'https://github.com'
        },
        {
          name: 'Yuga博客1',
          url: 'https://yugasun.com'
        }
      ]
    }
  },
  methods: {
    addItem () {
      this.count++
      this.list.push({
        name: 'Yuga博客' + this.count,
        url: 'https://yugasun.com'
      })
    }
  }
})

上面 Vue 實例在初始化的時候,屬性 data 中定義了一個站點列表,然后模板中通過 v-for 指令進行列表渲染,同時也使用了 v-bind 指令來進行屬性綁定,并且通過 v-on 指令來監聽按鈕的 click 事件來執行 addItem 方法。當點擊添加按鈕,就會向 list push一條數據,視圖會再次更新。(關于指令相關文章將在下一篇中講到,感興趣的同學可以先到官網學習了解。)

Demo2: computed

在實際開發過程中,我們的接口請求到的數據往往會差強人意,這時就需要我們進行一些轉化,來生成我們想要的數據結構,當然最直接的方式就是每次請求完數據就通過固定函數處理一遍,但是這個得手動執行。此時 computed 計算屬性就可以用上了。

我們知道除了 data 中定義的數據可以再模板中使用外, computed 屬性也可以。只不過 computed 中的屬性是需要先進行計算,然后再返回想要的數據的。當我們輸出某個屬性,必須依賴另外一個 data 中的屬性來動態計算獲得的,此時使用 computed 就非常簡單了。代碼如下:

var app = new Vue({
  el: "#app",
  data () {
    return {
      count: 1,
      // 實際開發中往往是通過接口請求獲取
      requestList: [
        'Vuejs官網-https://cn.vuejs.org',
        'Github-https://github.com',
        'Yuga博客1-https://yugasun.com'
      ]
    }
  },
  computed: {
    list: function(){
      var list = [];
      this.requestList.map(function(item, index){
        var tempArr = item.split('-');
        list.push({
          name: tempArr[0],
          url: tempArr[1]
        });
      })
      return list;  
    }
  },
  methods: {
    addItem () {
      this.count++
      this.requestList.push('Yuga博客' + this.count + '-https://yugasun.com')
    }
  }
})

關于計算屬性 - computed

關于計算屬性,其實有個細節是很多同學沒有注意到的,計算屬性實際上是可以修改的!那么如何才能修改呢?

其實計算屬性不僅可以定義為一個函數,也可以定義為一個含有 get/set 屬性的對象。當我們定義為一個函數是,Vue 內部會默認將這個函數賦值給 get 屬性,一般 set 是未定義的。當我們定義 set 屬性后,就可以對它進行修改了。來看下面一段代碼:

<divid="app">
  <buttonv-on:click="changeName">改變姓名</button>
  <h2>{{ username }}</h2>
</div>
var app = new Vue({
  el: "#app",
  data () {
    return {
      firstName: 'Yuga',
      lastName: 'Sun'
    }
  },
  computed: {
    username: {
      get: function(){
        return this.firstName + ' ' +  this.lastName;
      },
      set: function(newVal){
        var names = newVal.split(' ');
        this.firstName = names[0];
        this.lastName = names[1];
      }
    }
  },
  methods: {
    changeName () {
      if (this.username === 'Yuga Sun') {
        this.username = 'Summer Wu';
      } else {
        this.username = 'Yuga Sun';
      }
    }
  }
})

當進行賦值操作 this.username = 'Summer Wu' 時,計算屬性 username 的 set 函數就會被調用,同時也對 firstName 和 lastName 進行了相應的更新。這里看似是直接進行賦值操作,其實也是通過間接修改 firstName 和 lastName 來實現 username 的更新的。因為 計算屬性是基于它依賴的值進行緩存的 ,如果它依賴的值沒有發生改變,它自己就沒法發生改變。

關于偵聽器 - watch

創建 Vue 應用時,我們還提到過 watch 這個屬性,它其實是個對象,鍵是需要觀察的表達式,值是對應的回調函數。值也可以是方法名,或者包含選項的對象。和上面的計算屬性類似,他可以監聽 值/表達式 的變化來執行回調函數。我們先實現上面的功能:

var app = new Vue({
  el: "#app",
  data () {
    return {
      firstName: 'Yuga',
      lastName: 'Sun',
      username: 'Yuga Sun'
    }
  },
  watch: {
    firstName: function(val, oldVal){
      this.username = val + ' ' + this.lastName;
    },
    lastName: function (val, oldVal){
      this.username = this.firstName + ' ' + val;
    }
  },
  methods: {
    changeName () {
      if (this.username === 'Yuga Sun') {
        this.firstName = 'Summer';
        this.lastName = 'Wu';
      } else {
        this.firstName = 'Yuga';
        this.lastName = 'Sun';
      }
    }
  }
})

以上就是最基礎用法,往往有些時候我們的監聽屬性并沒有那么簡單。往往是一個對象,這時當我們修改該對象的屬性時,如何實現監聽呢?先看下面代碼:

<divid="app">
  <buttonv-on:click="changeName">改變姓名</button>
  <h4>{{ username }}</h4>
</div>
var app = new Vue({
  el: "#app",
  data () {
    return {
      userinfo: {
        firstName: 'Yuga',
        lastName: 'Sun',
      },
      username: 'Yuga Sun'
    }
  },
  watch: {
    userinfo: function (val, oldVal){
      this.username = val.firstName + ' ' + val.lastName;
    }
  },
  methods: {
    changeName () {
      if (this.username === 'Yuga Sun') {
        this.userinfo.firstName = 'Summer'
        this.userinfo.lastName = 'Wu'
      } else {
        this.userinfo.firstName = 'Yuga'
        this.userinfo.lastName = 'Sun'
      }
    }
  }
})

此時無論我們如何點擊按鈕,都無法改變 username 的值,因為 watch 偵聽器默認只是偵聽該對象本身的賦值操作,也就是直接對 this.userinfo 進行賦值操作時的變化,并未對其內部屬性進行偵聽。實際上對于偵聽的值是可以為一個對象的,它還有個 deep 屬性,用來設置是否偵聽內部屬性的變化,而回調函數是通過 handler 來設置的。我們再次修改代碼如下:

var app = new Vue({
  el: "#app",
  data () {
    return {
      userinfo: {
        firstName: 'Yuga',
        lastName: 'Sun',
      },
      username: 'Yuga Sun'
    }
  },
  watch: {
    userinfo: {
      deep: true,
      handler: function (val, oldVal){
        this.username = val.firstName + ' ' + val.lastName;
      }
    }
  },
  methods: {
    changeName () {
      if (this.username === 'Yuga Sun') {
        this.userinfo.firstName = 'Summer'
        this.userinfo.lastName = 'Wu'
      } else {
        this.userinfo.firstName = 'Yuga'
        this.userinfo.lastName = 'Sun'
      }
    }
  }
})

點擊按鈕,你會發現 username 可以根據按鈕點擊更新了,這個屬性再實際項目中非常實用,因為往往我們修改數據時,并不是整體賦值,大部分時候都是局部修改屬性的,所以這個時候就需要通過設置 deep 屬性為 true ,來達到我們的偵聽目的。

問題來了,當偵聽對象包含很多屬性,而我們只是需要監聽其中的一個或某幾個屬性,這時如果我們通過這種方式偵聽所有內部屬性的變化,自然就會造成內存的浪費。那么能不能只偵聽單一內部屬性的變化呢?答案是肯定的。

其實在 watch 定義的時候, 鍵是可以為需要觀察的表達式 的, 表達式 就是說明,我們是可以寫成對象屬性訪問表達式的。比如我們只需要偵聽姓氏的修改,我們可以寫成 userinfo.lastName 。再來看下面代碼:

var app = new Vue({
  el: "#app",
  data () {
    return {
      userinfo: {
        firstName: 'Yuga',
        lastName: 'Sun',
      },
      username: 'Yuga Sun'
    }
  },
  watch: {
    'userinfo.lastName': function (val, oldVal){
      this.username = this.userinfo.firstName + ' ' + val;
    }
  },
  methods: {
    changeName () {
      if (this.username === 'Yuga Sun') {
        this.userinfo.lastName = 'Wu'
      } else {
        this.userinfo.lastName = 'Sun'
      }
    }
  }
})

這樣我們就可以根據項目實際情況,靈活的使用偵聽器來偵聽我們所關注的屬性了,趕緊動手試一試吧~

源碼在此

 

來自:https://yugasun.com/post/you-dont-know-vuejs-2.html

 

 本文由用戶 rcoq9270 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!