React和Vue中监听变量变化的方法(3)

/** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } /core/observer/index.js defineReactive 方法最为核心,它将set和get方法改写,如果我们重新对变量进行赋值,那么会判断变量的新值是否等于旧值,如果不相等,则会触发 dep.notify() 从而回调watch中的方法。 /** * Define a reactive property on an Object. */ export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { // dep当中存放的是watcher数组 const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { // 如果第三个值没有传。那么val就直接从obj中根据key的值获取 val = obj[key] } let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, // 可设置值 configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { // dep中生成个watcher dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, // 重点看set方法 set: function reactiveSetter (newVal) { // 获取变量原始值 const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ // 进行重复值比较 如果相等直接return if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { // dev环境可以直接自定义set customSetter() } // 将新的值赋值 if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // 触发watch事件 // dep当中是一个wacher的数组 // notify会执行wacher数组的update方法,update方法触发最终的watcher的run方法,触发watch回调 dep.notify() } }) }

小程序

自定义Watch

小程序的data本身是不支持watch的,但是我们可以自行添加,我们参照 Vue 的写法自己写一个。

watcher.js

export function defineReactive (obj, key, callbackObj, val) { const property = Object.getOwnPropertyDescriptor(obj, key); console.log(property); const getter = property && property.get; const setter = property && property.set; val = obj[key] const callback = callbackObj[key]; Object.defineProperty(obj, key, { enumerable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val return value }, set: (newVal) => { console.log('start set'); const value = getter ? getter.call(obj) : val if (typeof callback === 'function') { callback(newVal, val); } if (setter) { setter.call(obj, newVal) } else { val = newVal } console.log('finish set', newVal); } }); } export function watch(cxt, callbackObj) { const data = cxt.data for (const key in data) { console.log(key); defineReactive(data, key, callbackObj) } }

使用

我们在执行watch回调前没有对新老赋值进行比较,原因是微信当中对data中的变量赋值,即使给引用变量赋值还是相同的值,也会因为引用地址不同,判断不相等。如果想对新老值进行比较就不能使用 === ,可以先对obj或者array转换为json字符串再比较。

//index.js //获取应用实例 const app = getApp() import {watch} from '../../utils/watcher'; Page({ data: { motto: 'hello world', userInfo: {}, hasUserInfo: false, canIUse: wx.canIUse('button.open-type.getUserInfo'), tableData: [] }, onLoad: function () { this.initWatcher(); }, initWatcher () { watch(this, { motto(newVal, oldVal) { console.log('newVal', newVal, 'oldVal', oldVal); }, userInfo(newVal, oldVal) { console.log('newVal', newVal, 'oldVal', oldVal); }, tableData(newVal, oldVal) { console.log('newVal', newVal, 'oldVal', oldVal); } }); }, onClickChangeStringData() { this.setData({ motto: 'hello' }); }, onClickChangeObjData() { this.setData({ userInfo: { name: 'helo' } }); }, onClickChangeArrayDataA() { const tableData = []; this.setData({ tableData }); } })

参考

如何阅读React源码

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/6bc0a400373d6d61ccf96d169506a48c.html