在Vue中,对于state中的数据 Vue 提供了一种通用的方式来观察和响应 Vue 实例上的数据变动:监听属性 watch。
在小程序的开发工作中遇到了同样的情况:需要对当前页面data中的某一个或者几个数据进行实时监听
如果能有一个通用方法进行监听,可以统一解决类似的问题。
知到Vue 双向数据绑定的打工人肯定都知道,其原理是数据的劫持,在小程序中想要做到肯定也是一样的啦!所以这里需要用到Javascript中的Object.defineProperty() 方法,来手动劫持对象的getter/setter,还不熟悉的打工人
请戳链接Object.defineProperty 介绍
回到小程序,想要达到通用,就需要将这个监听方法写成工具类,并在入口app.js中引用,挂载到全局,在具体页面中 getApp().方法名(参数) 调用,调用的时机就在页面生命周期 onLoad 中
setWatcher、observe 为监听逻辑 ,watch 为Page({}) 中定义的一个属性,配置函数名为需要监听的值
//index.js
Page({
data: {
count: 0
},
onLoad(){
getApp().setWatcher(this.data, this.watch); // 设置监听
},
// 页面中写一个按钮去触发这个事件改变data.count
handleChange(){
let val = this.data.count
this.setData({ count: ++val })
}
// 监听事件
setWatcher(data, watch){
Object.keys(watch).forEach(key => {
this.observe(data, key, watch[key])
})
},
observe(obj, key , watchFun){
let val = obj[key]
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
set: function(value){
watchFun(value, val)
val = value
},
get: function(){
return val
}
})
},
// watch 属性,设置需要监听的属性
watch:{
count:function(newVal, oldVal){
console.log('newVal:',newVal);
console.log('oldVal:',oldVal);
},
}
})
到这里可以满足我们对简单数据的监听。这样并不够全面,要是监听的值是一个对象呢 ?那需要对我们的代码更进一步的完善,是进阶内容
Vue中 watch对象内不光是能写name、age这类属性,还能用引号写出’my.name’、'my.age’这类属性,即对my对象下的name、age进行监听。 我们只需要改进一下setWatcher方法:
/**
* 设置监听器
*/
setWatcher(data, watch) {
Object.keys(watch).forEach(v => {
let key = v.split('.'); // 将watch中的属性以'.'切分成数组
let nowData = data; // 将data赋值给nowData
for (let i = 0; i < key.length - 1; i++) { // 遍历key数组的元素,除了最后一个!
nowData = nowData[key[i]]; // 将nowData指向它的key属性对象
}
let lastKey = key[key.length-1];
// 假设key==='my.name',此时nowData===data['my']===data.my,lastKey==='name'
this.observe(nowData, lastKey,watch[v]); // 监听nowData对象的lastKey
})
}
此时我们可以监听到对象的具体某一个属性,代码此时可以这么写
//index.js
Page({
data: {
my:{
name:"xiaoming",
age:21
}
},
onLoad(){
getApp().setWatcher(this.data, this.watch);
this.data.my.name = 'uzi';
this.data.my.age = 18;
this.setData({
my: this.data.my
})
},
watch:{
'my.name':function(newValue){
console.log(newValue);
},
'my.age':function(newValue){
console.log(newValue);
}
}
})
在Vue中有一个深度监听的开关,接下来我们要在小程序中实现Vue中的深度监听,即监听my对象即可监听它的内部所有属性以及属性的属性以及属性的属性的属性…并且监听函数的this要指向这个page里的this。
/**
* 设置监听器
*/
setWatcher(page) {
let data = page.data;
let watch = page.watch;
Object.keys(watch).forEach(v => {
let key = v.split('.'); // 将watch中的属性以'.'切分成数组
let nowData = data; // 将data赋值给nowData
for (let i = 0; i < key.length - 1; i++) { // 遍历key数组的元素,除了最后一个!
nowData = nowData[key[i]]; // 将nowData指向它的key属性对象
}
let lastKey = key[key.length - 1];
// 假设key==='my.name',此时nowData===data['my']===data.my,lastKey==='name'
let watchFun = watch[v].handler || watch[v]; // 兼容带handler和不带handler的两种写法
let deep = watch[v].deep; // 若未设置deep,则为undefine
this.observe(nowData, lastKey, watchFun, deep, page); // 监听nowData对象的lastKey
})
},
/**
* 监听属性 并执行监听函数
*/
observe(obj, key, watchFun, deep, page) {
var val = obj[key];
// 判断deep是true 且 val不能为空 且 typeof val==='object'(数组内数值变化也需要深度监听)
if (deep && val != null && typeof val === 'object') {
Object.keys(val).forEach(childKey=>{ // 遍历val对象下的每一个key
this.observe(val,childKey,watchFun,deep,page); // 递归调用监听函数
})
}
var that = this;
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
set: function(value) {
// 用page对象调用,改变函数内this指向,以便this.data访问data内的属性值
watchFun.call(page,value,val); // value是新值,val是旧值
val = value;
if(deep){ // 若是深度监听,重新监听该对象,以便监听其属性。
that.observe(obj, key, watchFun, deep, page);
}
},
get: function() {
return val;
}
})
}