溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何實現(xiàn)微信小程序中的數(shù)據(jù)偵聽

發(fā)布時間:2021-08-02 13:44:01 來源:億速云 閱讀:126 作者:小新 欄目:web開發(fā)

這篇文章將為大家詳細(xì)講解有關(guān)如何實現(xiàn)微信小程序中的數(shù)據(jù)偵聽,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

在小程序項目中, 我們的通常會使用到使用到一個全局對象作為各個頁面通用的數(shù)據(jù)存儲容器, 將它綁定到app對象后, 就能在每一個頁面都自由的操縱這個對象. 然而在實踐中, 由于這個對象及其屬性不具備響應(yīng)式條件, 它不能直接參與業(yè)務(wù)邏輯的編寫, 能力僅僅局限于數(shù)據(jù)儲存. 若是在VueJS項目中, 我們可能經(jīng)常使用到 Vue.$watch 去偵聽某個數(shù)據(jù)是否發(fā)生變化, 小程序卻缺乏這種能力.

在這篇文章中, 我將用150行代碼, 手把手帶你打造一個小程序也可以使用的偵聽器(下簡稱VX):

// 一個快速賦值的語法糖函數(shù), 可以創(chuàng)建結(jié)構(gòu)為 { value: a { b: { val: ''} } } 的對象
vx.set('value.a.d', { val: '' })
// 對某個屬性進行偵聽, 如果發(fā)生改變, 則執(zhí)行相應(yīng)函數(shù)(可多次watch以執(zhí)行多個函數(shù))
vx.watch('value.a.d.val', newVal => {
 console.log(`val改變?yōu)?nbsp;: `, newVal)
})
value.a.d.val = 3 // val改編為 : 3

使用VX偵聽器, 我們可以更加方便的管理各個頁面的狀態(tài). 同時, 我們憑借 watch 語法, 可以更優(yōu)雅地編寫業(yè)務(wù)邏輯.

坐穩(wěn)了, 三輪車準(zhǔn)備啟動了~ 各位評論見~ :yum:

稍微理一理思路

在全局對象中, 我們不一定要對每一個屬性都進行偵聽, 所以VX主要的功能就是通過set去設(shè)置某個具體屬性的setter/getter, 同時通過watch向添加該屬性添加需要訂閱的回調(diào)函數(shù).

依賴對象的實現(xiàn)

首先我們需要造一個通用的 依賴對象 , 依賴對象攜帶一個訂閱數(shù)組用于存放一組回調(diào)函數(shù), 同時它還應(yīng)該包括一些操作訂閱數(shù)組能力(如添加訂閱, 清空訂閱)的函數(shù)

class Dep {
 constructor () {
 this.subs = []
 }
 // 將回調(diào)添加到數(shù)組中
 addSub (fn) { /*...*/ }
 delSub (fn) { /*...*/ }
 // 執(zhí)行數(shù)組中每一項函數(shù)
 notify (newVal, oldVal) {
 this.subs.forEach(func => func(newVal, oldVal))
 }
}

全局對象中每一個響應(yīng)式屬性(及其每一個子屬性), 都應(yīng)該和一個新的Dep實例保持一一對應(yīng)的關(guān)系, 這樣我們進行偵聽變化, 執(zhí)行訂閱的回調(diào)函數(shù)時, 只需要找到對應(yīng)的實例執(zhí)行 notify 通知更新即可.

設(shè)置響應(yīng)式屬性

defineProperty

可能是因為接觸DefineProperty要比接觸Proxy早一些的緣故, 代碼使用了前者進行響應(yīng)式的實現(xiàn), Object.defineProperty方法會直接在一個對象上定義一個新屬性, 這里快速過一遍 defineProperty 具體配置:

// @param obj 要在其上定義屬性的對象
// @param key 要定義或修改的屬性的名稱
Object.defineProperty(obj, key, {
 // 該屬性是否能被枚舉
 enumerable: true,
 // 該屬性能否被刪除
 configurable: true,
 // 訪問該屬性則會執(zhí)行此方法
 get: () => {
 return val
 },
 // 修改該屬性時會執(zhí)行此方法
 set: newVal => {
 val = newVal
 },
 // value & writeble 不能和 getter/setter 同時出現(xiàn)
})

通過對defineProperty進行上層封裝, 我們可以輕松的實現(xiàn)在全局對象上設(shè)置響應(yīng)式屬性功能, 在此, 我們結(jié)合剛才定義的Dep對象, 將一個新的dep實例綁定到新增屬性的setter中:

set (key, val, options = {}, obj = this.store) {
 const dep = new Dep()
 Object.defineProperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: () => {
  return val
 },
 set: newVal => {
  if (newVal === val) {
  return
  }
  dep.notify(newVal, val)
  val = newVal
 }
 })
}

每當(dāng)對應(yīng)屬性被賦值, 就會執(zhí)行依賴數(shù)組中的回調(diào)函數(shù).

不過這樣還不夠, 我們還得想辦法獲取到這個dep實例, 才能給它的依賴數(shù)組填充函數(shù).

這邊提供一個很簡單的思路, 并不推薦實踐中這么做:

set (key, val, options = {}, obj = this.store) {
 const dep = new Dep()
 Object.defineProperty(obj, key, {})
+ return dep
}
const valueDep = set('value', b, {})
valueDep.addSub(() => { console.log('value changed!') })

雖然代碼能使用了, 就是是看起來怪怪的~ :yum: 我們的三輪車開進了岔路~

通過watch添加訂閱

喝口水我們繼續(xù)

<黑客與畫家>一書中曾經(jīng)提到這樣一個觀點, 我深有體會:

如果你覺得你的代碼奇怪, 那么往往它是錯的

上面的那一串代碼僅僅是能跑通的水平, 我們需要加入更多的細(xì)節(jié)和思考, 有時候只需要坐下來稍微看一下代碼, 就會有各種想法蹦出來:

構(gòu)思這種東西有一個特點,那就是它會導(dǎo)致更多的構(gòu)思。你有沒有注意過,坐下來寫東西的時候,一半的構(gòu)思是寫作時產(chǎn)生的?

隱藏Dep

這些內(nèi)容應(yīng)和外部是解耦的. 首先一點, 我們創(chuàng)建一個偵聽器類, 用于封裝我們偵聽所用到的所有方法, 它包含了我們想要的全局對象以及操作它的方法(如watch,set):

class VX {
 constructor () {
 this.store = Object.create(null)
 }
 watch (key, fn, obj = this.store) {}
 set (key, val, options = {}, obj = this.store) {}
}
const vx = new VX()

我們可以在watch中給對象某個屬性添加回調(diào), 就不用去直接操作Dep依賴數(shù)組了. 只是, 我們在業(yè)務(wù)代碼中調(diào)用watch, 要怎么去獲取obj.key對應(yīng)的dep呢?

我們設(shè)置一個全局的depHandler, 在obj.key的getter中主動將depHandler設(shè)置為當(dāng)前obj.key的dep實例, 那么我們在watch函數(shù)里, 只要用任意操作觸發(fā)obj.key的getter, 就能通過depHandler得到它的dep實例了, 代碼形如:

+ // 一開始沒有持有dep實例
+ let handleDep = null
 class VX {
 watch (key, fn, obj = this.store) {
+  console.log(obj.key) // 使用任意操作觸發(fā)obj.key的getter, 那么handleDep將自動引用obj.key的dep實例
+  handleDep.addSub(fn)
 }
 set (key, val, options = {}, obj = this.store) {
  const dep = new Dep()
  Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: () => {
+   handleDep = dep
   return val
  },
  set: newVal => {}
  })
 }
 }

主動收集依賴

我們增加 handleDep.addSub(fn) 添加回調(diào)函數(shù)的邏輯, 其實可以直接放到getter中, 首先在Dep類中封裝一個'主動'收集依賴的 collect 方法, 他會將全局 handleFn 存放到訂閱數(shù)組中, 這樣一來, 在watch函數(shù)中, 我們只要觸發(fā)obj.key的getter, 就可以主動收集依賴了:

let handleFn = null
class Dep {
 addSub (fn) {}
 delSub (fn) {}
 clear () {}
 collect (fn = handleFn) {
 if (fn && !this.subs.find(x => x === fn)) {
  this.addSub(fn)
 }
 }
 notify (newVal, oldVal) {}
}

let handleDep = null
class VX {
 watch (key, fn, obj = this.store) {
 handleFn = fn
 console.log(obj.key)
 }
 set (key, val, options = {}, obj = this.store) {
 const dep = new Dep()
 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: () => {
  handleDep = dep
  handleDep.collect()
  return val
  },
  set: newVal => {}
 })
 }
}

處理key值為對象鏈的情況

在先前的watch函數(shù)中, 我們使用console.log(obj.key)去觸發(fā)對應(yīng)屬性的getter, 如果我們調(diào)用方式是 watch('a.b.c') 就無能為力了. 這里我們封裝一個通用方法, 用于處理對象鏈字符串的形式:

// 通過將字符串'a.b'分割為['a', 'b'], 再使用一個while循環(huán)就可以走完這個對象鏈
function walkChains (key, obj) {
 const segments = key.split('.')
 let deepObj = obj
 while (segments.length) {
 deepObj = deepObj[segments.shift()]
 }
}
class VX {
 watch (key, fn, obj = this.store) {
 handleFn = fn
 walkChains(key, obj)
 }
}

在set方法中處理對象鏈字符串稍微有些不同, 因為如果 set('a.b') 時, 沒有在我們?nèi)謱ο笾姓业絘屬性, 這里應(yīng)該拋錯.

實際的處理中, 需要推斷'obj.a'以及'obj.a.b'是否存在. 假設(shè)沒有'obj.a', 那么我們應(yīng)該創(chuàng)建一個新的對象, 并且給新的對象添加屬性'b', 所以代碼類似 walkChains 函數(shù), 只是稍作一層判斷:

set (key, val, obj) {
 const segments = key.split('.')
 // 這里需要注意, 我們只處理到倒數(shù)第二個屬性
 while (segments.length > 1) {
 const handleKey = segments.shift()
 const handleVal = obj[handleKey]
 // 存在'obj.a'的情況
 if (typeof handleVal === 'object') {
  obj = handleVal
 // 不存在'obj.a'則給a屬性賦一個非響應(yīng)式的對象
 } else if (!handleVal) {
  obj = (
  key = handleKey,
  obj[handleKey] = {},
  obj[handleKey]
  )
 } else {
  console.trace('already has val')
 }
 }
 // 最后一個屬性要手動賦值
 key = segments[0]
}

業(yè)務(wù)場景應(yīng)用

小程序跨頁面刷新數(shù)據(jù)

我們經(jīng)常碰到在小程序中由A頁面跳轉(zhuǎn)到B頁面, 如果B頁面進行了一些操作, 希望A頁面自動刷新數(shù)據(jù)的情況. 但是由于A頁面跳轉(zhuǎn)到B頁面不同(也許是redirect,也許是navigate), 處理方法也不盡相同.

使用navigate方式跳轉(zhuǎn)后, A頁面不會被注銷, 所以我們一般會通過全局對象去貯存A頁面實例(也就是A頁面的this對象), 然后在B頁面直接調(diào)用相應(yīng)的方法(如A.refreshList())進行刷新操作.

引入VX后, 我們可以在 onload 生命周期直接調(diào)用watch方法添加訂閱:

// app.js
import VX from '@/utils/suites/vx'
const vx = new VX()
app.vx = vx
app.store = vx.store
app.vx.set('userType', '商戶')

// page a
onLoad () {
 app.vx.watch('userType', userType => {
 if (userType === '商戶') {
  // ...
 } else if (userType === '管理員') {
  // ...
 }
 }, {
 immediate: true
 })
}

// page b
switchUserType () {
 app.store.userType = '管理員'
}

可能遇到的問題

給watch方法添加的函數(shù)設(shè)置立即執(zhí)行

有的時候我們希望通過watch添加函數(shù)的同時還立即執(zhí)行該函數(shù)一次, 這個時候我們需要再定義額外的參數(shù)傳遞到watch中. 問題是這個函數(shù)不一定是同步函數(shù).

簡單處理如下:

class VX {
 async watch (key, fn, options = { immediately: false }, obj = this.store) {
 handleDep = fn
 walkChains(key, obj)
 options.immediately && await fn(options.defaultParams)
 }
}

this綁定丟失問題

在我在對VX進行刪除屬性方法的擴展時, 我往walkChain函數(shù)中添加了一個執(zhí)行回調(diào)函數(shù)的機制, 并且在刪除屬性這個方法直接調(diào)用了walkChain:

+ function walkChains (key, obj, fn) {
 const segments = key.split('.')
 let deepObj = obj
 while (segments.length) {
  deepObj = deepObj[segments.shift()]
+  fn && fn()
 }
 }
del (key, obj = this.store) {
 walkChains(key, obj, handleDep.clear)
 delete obj[key]
}

因為handleDep.clear當(dāng)成參數(shù)傳遞進walkChains中會 丟失this綁定 , 所以上面那段代碼其實是有問題的, 不過稍作修改就好了:

del (key, obj = this.store) {
+ walkChains(key, obj, () => handleDep.clear())
 delete obj[key]
 }

關(guān)于“如何實現(xiàn)微信小程序中的數(shù)據(jù)偵聽”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI