回應系統是 Vue 一個顯著功能,修改屬性,可以更新視圖,這讓狀態管理變得非常簡單且直覺。
建立 Vue 實例時,Vue 將遍歷 data 的屬性,透過 ES5 的 Object.defineProperty 將它們轉為 getter/setter,在其內部 Vue 可以追蹤依賴、通知變更。
const vm = new Vue({ data: {foo: 1} // 'vm.foo' (在内部,同 'this.foo') 是响应的 })
觀察屬性變化
Vue 的實例提供了 $watch 方法,用於觀察屬性變化。
const vm = new Vue({ data: {foo: 1} }) vm.$watch('foo', function (newValue, oldValue) { console.log(newValue, oldValue) // 输出 2 1 console.log(this.foo) // 输出 2 }) vm.foo = 2
當屬性變化後,響應函數將會被調用,在其內部,this 自動綁定到 Vue 的實例 vm 上。
需要注意的是,反應是異步的。如下:
const vm = new Vue({ data: {foo: 1} }) vm.$watch('foo', function (newValue, oldValue) { console.log('inner:', newValue) // 后输出 "inner" 2 }) vm.foo = 2 console.log('outer:', vm.foo) // 先输出 "outer" 2
透過 $watch Vue 實現了資料和視圖的綁定。觀察到資料變化,Vue 便非同步更新 DOM ,在同一事件循環內,多次資料變更將會被快取起來,在下次事件循環中,Vue 刷新佇列並僅執行必要的更新。如下:
const vm = new Vue({ data: {foo: 1} }) vm.$watch('foo', function (newValue, oldValue) { console.log('inner:', newValue) // 后只输出一次 "inner" 5 }) vm.foo = 2 vm.foo = 3 vm.foo = 4 console.log('outer:', vm.foo) // 先输出 "outer" 4 vm.foo = 5
計算屬性
MV* 中,將 Model 層資料展現到 View,經常有複雜的資料處理邏輯,這種情況下,使用計算屬性 (computed property) 更加明智。
const vm = new Vue({ data: { width: 0, height: 0, }, computed: { area () { let output = '' if (this.width > 0 && this.height > 0) { const area = this.width * this.height output = area.toFixed(2) + 'm²' } return output } } }) vm.width = 2.34 vm.height = 5.67 console.log(vm.area) // 输出 "13.27m²"
在計算屬性內部,this 自動綁定 vm,因此宣告計算屬性時需避免使用箭頭函數。
上例中,vm.width 和 vm.height 是回應的,vm.area 內部首次讀取 this.width 和 this.height 時,Vue 收集其做為 vm.area height 變化時,vm.area 重新求值。
計算屬性是基於它的依賴緩存,如果 vm.width 和 vm.height 沒有變化,請多次讀取 vm.area,則會立即返回先前的計算結果,而不必再求值。
同樣由於 vm.width 和 vm.height 是回應的,在 vm.area 中可以將依賴的屬性賦值給一個變量,透過讀取變數來減少讀取屬性次數,同時解決在條件分支中,Vue 有時會無法收集到依賴的問題。
新的實作如下:
const vm = new Vue({ data: { width: 0, height: 0, }, computed: { area () { let output = '' const {width, height} = this if (width > 0 && height > 0) { const area = width * height output = area.toFixed(2) + 'm²' } return output } } }) vm.width = 2.34 vm.height = 5.67 console.log(vm.area) // 输出 "13.27m²"
透過 ob.js 單獨使用 Vue 的屬性觀察模組
為方便學習和使用,ob.js 將 Vue 中屬性觀察模組提取並封裝了一下。
安裝
npm install --save ob.js
觀察屬性變化
const target = {a: 1} ob(target, 'a', function (newValue, oldValue) { console.log(newValue, oldValue) // 3 1 }) target.a = 3
加入計算屬性
const target = {a: 1} ob.compute(target, 'b', function () { return this.a * 2 }) target.a = 10 console.log(target.b) // 20
像聲明 Vue 實例一樣傳入參數集合
const options = { data: { PI: Math.PI, radius: 1, }, computed: { 'area': function () { return this.PI * this.square(this.radius) }, }, watchers: { 'area': function (newValue, oldValue) { console.log(newValue) // 28.274333882308138 }, }, methods: { square (num) { return num * num }, }, } const target = ob.react(options) target.radius = 3