在微信小程序里使用watch和computed的方法
在開(kāi)發(fā) vue 的時(shí)候,我們可以使用 watch 和 computed 很方便的檢測(cè)數(shù)據(jù)的變化,從而做出相應(yīng)的改變,但是在小程序里,只能在數(shù)據(jù)改變時(shí)手動(dòng)觸發(fā) this.setData() ,那么如何給小程序也加上這兩個(gè)功能呢?
我們知道在 vue 里是通過(guò) Object.defineProperty 來(lái)實(shí)現(xiàn)數(shù)據(jù)變化檢測(cè)的,給該變量的 setter 里注入所有的綁定操作,就可以在該變量變化時(shí)帶動(dòng)其它數(shù)據(jù)的變化。那么是不是可以把這種方法運(yùn)用在小程序上呢?
實(shí)際上,在小程序里實(shí)現(xiàn)要比 vue 里簡(jiǎn)單,應(yīng)為對(duì)于 data 里對(duì)象來(lái)說(shuō),vue 要遞歸的綁定對(duì)象里的每一個(gè)變量,使之響應(yīng)式化。但是在微信小程序里,不管是對(duì)于對(duì)象還是基本類型,只能通過(guò) this.setData() 來(lái)改變,這樣我們只需檢測(cè) data 里面的 key 值的變化,而不用檢測(cè) key 值里面的 key 。
先上測(cè)試代碼
<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)在我們要實(shí)現(xiàn) watch 和 computed 方法,使得 test 變化時(shí),test1、test2、test3 也變化,為此,我們?cè)黾恿艘粋€(gè)按鈕,當(dāng)點(diǎn)擊這個(gè)按鈕時(shí),test 會(huì)改變。
watch 方法相對(duì)簡(jiǎn)單點(diǎn),首先我們定義一個(gè)函數(shù)來(lái)檢測(cè)變化:
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ù)傳入的對(duì)象,給每個(gè)鍵調(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。
接著來(lái)看 computed,這個(gè)稍微復(fù)雜,因?yàn)槲覀儫o(wú)法得知 computed 里依賴的是 data 里面的哪個(gè)變量,因此只能遍歷 data 里的每一個(gè)變量。
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)
}
詳細(xì)解釋下這段代碼,首先給 data 里的每個(gè)屬性調(diào)用 defineReactive 方法。接著計(jì)算 computed 里面每個(gè)屬性第一次的值,也就是上例中的 test2、test3。
computed(this, {
test2: function() {
return this.data.test.a + '2222222'
},
test3: function() {
return this.data.test.a + '3333333'
}
})
這里分別調(diào)用 test2 和 test3 的值,將返回值與對(duì)應(yīng)的 key 值組合成一個(gè)對(duì)象,然后再調(diào)用 setData() ,這樣就會(huì)第一次計(jì)算這兩個(gè)值,這里使用了 reduce 方法。但是你可能會(huì)發(fā)現(xiàn)其中這兩行代碼,它們好像都沒(méi)有被提到是干嘛用的。
ctx.data.$target = function() {
ctx.setData({ [next]: obj[next].call(ctx) })
}
ctx.data.$target = null
可以看到,test2 和 test3 都是依賴 test 的,這樣必須在 test 改變的時(shí)候在其的 setter 函數(shù)中調(diào)用 test2 和 test3 中對(duì)應(yīng)的函數(shù),并通過(guò) setData 來(lái)設(shè)置這兩個(gè)變量。為此,需要將 defineReactive 改動(dòng)一下。
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 因?yàn)榇藭r(shí) this.data 還沒(méi)更新
setTimeout(() => {
subs.forEach(sub => sub())
}, 0)
}
val = newVal
},
})
}
相較于之前,增加了幾行代碼,我們聲明了一個(gè)變量來(lái)保存所有在變化時(shí)需要執(zhí)行的函數(shù),在 set 時(shí)執(zhí)行每一個(gè)函數(shù),因?yàn)榇藭r(shí) this.data.test 的值還未改變,使用 setTimeout 在下一輪再執(zhí)行?,F(xiàn)在就有一個(gè)問(wèn)題,怎么將函數(shù)添加到 subs 中。不知道各位還是否記得上面我們說(shuō)到的在 reduce 里的那兩行代碼。因?yàn)樵趫?zhí)行計(jì)算 test1 和 test2 第一次 computed 值的時(shí)候,會(huì)調(diào)用 test 的 getter 方法,此刻就是一個(gè)好機(jī)會(huì)將函數(shù)注入到 subs 中,在 data 上聲明一個(gè) $target 變量,并將需要執(zhí)行的函數(shù)賦值給該變量,這樣在 getter 中就可以判斷 data 上有無(wú) target 值,從而就可以 push 進(jìn) subs,要注意的是需要馬上將 target 設(shè)為 null,這就是第二句的用途,這樣就達(dá)到了一石二鳥(niǎo)的作用。當(dāng)然,這其實(shí)就是 vue 里的原理,只不過(guò)這里沒(méi)那么復(fù)雜。
到此為止已經(jīng)實(shí)現(xiàn)了 watch 和 computed,但是還沒(méi)完,有個(gè)問(wèn)題。當(dāng)同時(shí)使用這兩者的時(shí)候,watch 里的對(duì)象的鍵也同時(shí)存在于 data 中,這樣就會(huì)重復(fù)在該變量上調(diào)用 Object.defineProperty ,后面會(huì)覆蓋前面。因?yàn)檫@里不像 vue 里可以決定兩者的調(diào)用順序,因此我們推薦先寫(xiě) computed 再寫(xiě) watch,這樣可以 watch computed 里的值。這樣就有一個(gè)問(wèn)題,computed 會(huì)因覆蓋而無(wú)效。
思考一下為什么?
很明顯,這時(shí)因?yàn)橹暗?subs 被重新聲明為空數(shù)組了。這時(shí),我們想一個(gè)簡(jiǎn)單的方法就是把之前 computed 里的 subs 存在一個(gè)地方,下一次調(diào)用 defineReactive 的時(shí)候看對(duì)應(yīng)的 key 是否已經(jīng)有了 subs,這樣就可以解決問(wèn)題。修改一下代碼。
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 因?yàn)榇藭r(shí) this.data 還沒(méi)更新
setTimeout(() => {
subs.forEach(sub => sub())
}, 0)
}
val = newVal
},
})
}
這樣,我們就一步一步的實(shí)現(xiàn)了所需的功能。完整的代碼和例子請(qǐng)戳。
雖然經(jīng)過(guò)了一些測(cè)試,但不保證沒(méi)有其它未知錯(cuò)誤,歡迎提出問(wèn)題。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
javascript背景時(shí)鐘實(shí)現(xiàn)方法
這篇文章主要介紹了javascript背景時(shí)鐘實(shí)現(xiàn)方法,涉及javascript時(shí)間及頁(yè)面元素樣式的相關(guān)操作技巧,需要的朋友可以參考下2015-06-06
原生js實(shí)現(xiàn)復(fù)制對(duì)象、擴(kuò)展對(duì)象 類似jquery中的extend()方法
jq的extend()方法能很方便的實(shí)現(xiàn)擴(kuò)展對(duì)象方法,這里要實(shí)現(xiàn)的是:原生js實(shí)現(xiàn)復(fù)制對(duì)象,擴(kuò)展對(duì)象,類似jq中的extend()方法,需要的朋友可以參考下2014-08-08
JavaScript實(shí)現(xiàn)換膚效果(換背景)
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)換膚效果,即換背景功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
快速解決js中window.location.href不工作的問(wèn)題
下面小編就為大家?guī)?lái)一篇快速解決js中window.location.href不工作的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11
一篇文章帶你詳細(xì)了解JavaScript數(shù)組
本文是小編給大家特意整理的關(guān)于JavaScript數(shù)組的知識(shí),非常實(shí)用,在面試筆試題中經(jīng)常用得到,有需要的朋友可以參考下2021-09-09
JavaScript實(shí)現(xiàn)將文本框的值插入指定位置的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)將文本框的值插入指定位置的方法,涉及javascript節(jié)點(diǎn)添加操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08
JavaScript中三個(gè)等號(hào)和兩個(gè)等號(hào)的區(qū)別(== 和 ===)淺析
javascript中比較運(yùn)算符'=='與'==='可能大家用的比較多,但是大家對(duì)他的區(qū)別不是很清楚,接下來(lái)小編給大家介紹下js中三個(gè)等號(hào)和兩個(gè)等號(hào)的區(qū)別(== 和 ===),感興趣的朋友可以參考下2016-09-09
原生js實(shí)現(xiàn)一個(gè)放大鏡效果超詳細(xì)
這篇文章主要介紹了原生js實(shí)現(xiàn)一個(gè)放大鏡效果超詳細(xì),文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09

