欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

在微信小程序里使用watch和computed的方法

 更新時間:2018年08月02日 10:20:54   作者:fronter  
這篇文章主要介紹了在微信小程序里使用watch和computed的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

在開發(fā) vue 的時候,我們可以使用 watch 和 computed 很方便的檢測數(shù)據(jù)的變化,從而做出相應的改變,但是在小程序里,只能在數(shù)據(jù)改變時手動觸發(fā) this.setData() ,那么如何給小程序也加上這兩個功能呢?

我們知道在 vue 里是通過 Object.defineProperty 來實現(xiàn)數(shù)據(jù)變化檢測的,給該變量的 setter 里注入所有的綁定操作,就可以在該變量變化時帶動其它數(shù)據(jù)的變化。那么是不是可以把這種方法運用在小程序上呢?

實際上,在小程序里實現(xiàn)要比 vue 里簡單,應為對于 data 里對象來說,vue 要遞歸的綁定對象里的每一個變量,使之響應式化。但是在微信小程序里,不管是對于對象還是基本類型,只能通過 this.setData() 來改變,這樣我們只需檢測 data 里面的 key 值的變化,而不用檢測 key 值里面的 key 。

先上測試代碼

<view>{{ test.a }}</view>
<view>{{ test1 }}</view>
<view>{{ test2 }}</view>
<view>{{ test3 }}</view>
<button bindtap="changeTest">change</button>
const { watch, computed } = require('./vuefy.js')
Page({
 data: {
  test: { a: 123 },
  test1: 'test1',
 },
 onLoad() {
  computed(this, {
   test2: function() {
    return this.data.test.a + '2222222'
   },
   test3: function() {
    return this.data.test.a + '3333333'
   }
  })
  watch(this, {
   test: function(newVal) {
    console.log('invoke watch')
    this.setData({ test1: newVal.a + '11111111' })
   }
  })
 },
 changeTest() {
  this.setData({ test: { a: Math.random().toFixed(5) } })
 },
})

現(xiàn)在我們要實現(xiàn) watch 和 computed 方法,使得 test 變化時,test1、test2、test3 也變化,為此,我們增加了一個按鈕,當點擊這個按鈕時,test 會改變。

watch 方法相對簡單點,首先我們定義一個函數(shù)來檢測變化:

function defineReactive(data, key, val, fn) {
 Object.defineProperty(data, key, {
  configurable: true,
  enumerable: true,
  get: function() {
   return val
  },
  set: function(newVal) {
   if (newVal === val) return
   fn && fn(newVal)
   val = newVal
  },
 })
}

然后遍歷 watch 函數(shù)傳入的對象,給每個鍵調(diào)用該方法

function watch(ctx, obj) {
 Object.keys(obj).forEach(key => {
  defineReactive(ctx.data, key, ctx.data[key], function(value) {
   obj[key].call(ctx, value)
  })
 })
}

這里有參數(shù)是 fn ,即上面 watch 方法里 test 的值,這里把該方法包一層,綁定 context。

接著來看 computed,這個稍微復雜,因為我們無法得知 computed 里依賴的是 data 里面的哪個變量,因此只能遍歷 data 里的每一個變量。

function computed(ctx, obj) {
 let keys = Object.keys(obj)
 let dataKeys = Object.keys(ctx.data)
 dataKeys.forEach(dataKey => {
  defineReactive(ctx.data, dataKey, ctx.data[dataKey])
 })
 let firstComputedObj = keys.reduce((prev, next) => {
  ctx.data.$target = function() {
   ctx.setData({ [next]: obj[next].call(ctx) })
  }
  prev[next] = obj[next].call(ctx)
  ctx.data.$target = null
  return prev
 }, {})
 ctx.setData(firstComputedObj)
}

詳細解釋下這段代碼,首先給 data 里的每個屬性調(diào)用 defineReactive 方法。接著計算 computed 里面每個屬性第一次的值,也就是上例中的 test2、test3。

computed(this, {
 test2: function() {
  return this.data.test.a + '2222222'
 },
 test3: function() {
  return this.data.test.a + '3333333'
 }
})

這里分別調(diào)用 test2 和 test3 的值,將返回值與對應的 key 值組合成一個對象,然后再調(diào)用 setData() ,這樣就會第一次計算這兩個值,這里使用了 reduce 方法。但是你可能會發(fā)現(xiàn)其中這兩行代碼,它們好像都沒有被提到是干嘛用的。

 ctx.data.$target = function() {
  ctx.setData({ [next]: obj[next].call(ctx) })
 }
 
 ctx.data.$target = null

可以看到,test2 和 test3 都是依賴 test 的,這樣必須在 test 改變的時候在其的 setter 函數(shù)中調(diào)用 test2 和 test3 中對應的函數(shù),并通過 setData 來設(shè)置這兩個變量。為此,需要將 defineReactive 改動一下。

function defineReactive(data, key, val, fn) {
 let subs = [] // 新增
 Object.defineProperty(data, key, {
  configurable: true,
  enumerable: true,
  get: function() {
   // 新增
   if (data.$target) {
    subs.push(data.$target)
   }
   return val
  },
  set: function(newVal) {
   if (newVal === val) return
   fn && fn(newVal)
   // 新增
   if (subs.length) {
    // 用 setTimeout 因為此時 this.data 還沒更新
    setTimeout(() => {
     subs.forEach(sub => sub())
    }, 0)
   }
   val = newVal
  },
 })
}

相較于之前,增加了幾行代碼,我們聲明了一個變量來保存所有在變化時需要執(zhí)行的函數(shù),在 set 時執(zhí)行每一個函數(shù),因為此時 this.data.test 的值還未改變,使用 setTimeout 在下一輪再執(zhí)行?,F(xiàn)在就有一個問題,怎么將函數(shù)添加到 subs 中。不知道各位還是否記得上面我們說到的在 reduce 里的那兩行代碼。因為在執(zhí)行計算 test1 和 test2 第一次 computed 值的時候,會調(diào)用 test 的 getter 方法,此刻就是一個好機會將函數(shù)注入到 subs 中,在 data 上聲明一個 $target 變量,并將需要執(zhí)行的函數(shù)賦值給該變量,這樣在 getter 中就可以判斷 data 上有無 target 值,從而就可以 push 進 subs,要注意的是需要馬上將 target 設(shè)為 null,這就是第二句的用途,這樣就達到了一石二鳥的作用。當然,這其實就是 vue 里的原理,只不過這里沒那么復雜。

到此為止已經(jīng)實現(xiàn)了 watch 和 computed,但是還沒完,有個問題。當同時使用這兩者的時候,watch 里的對象的鍵也同時存在于 data 中,這樣就會重復在該變量上調(diào)用 Object.defineProperty ,后面會覆蓋前面。因為這里不像 vue 里可以決定兩者的調(diào)用順序,因此我們推薦先寫 computed 再寫 watch,這樣可以 watch computed 里的值。這樣就有一個問題,computed 會因覆蓋而無效。

思考一下為什么?

很明顯,這時因為之前的 subs 被重新聲明為空數(shù)組了。這時,我們想一個簡單的方法就是把之前 computed 里的 subs 存在一個地方,下一次調(diào)用 defineReactive 的時候看對應的 key 是否已經(jīng)有了 subs,這樣就可以解決問題。修改一下代碼。

function defineReactive(data, key, val, fn) {
 let subs = data['$' + key] || [] // 新增
 Object.defineProperty(data, key, {
  configurable: true,
  enumerable: true,
  get: function() {
   if (data.$target) {
    subs.push(data.$target)
    data['$' + key] = subs // 新增
   }
   return val
  },
  set: function(newVal) {
   if (newVal === val) return
   fn && fn(newVal)
   if (subs.length) {
    // 用 setTimeout 因為此時 this.data 還沒更新
    setTimeout(() => {
     subs.forEach(sub => sub())
    }, 0)
   }
   val = newVal
  },
 })
}

這樣,我們就一步一步的實現(xiàn)了所需的功能。完整的代碼和例子請戳

雖然經(jīng)過了一些測試,但不保證沒有其它未知錯誤,歡迎提出問題。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論