- 微信小程序watch监听, 类似vue的watch
Vue.js里有watch监听机制,很适合“一处改变,多处影响”的场景,在开发小程序的过程中,自然也会遇到这样的场景,下面介绍如何在小程序中实现watch监听 不好用你来打我! 一. 新建 watch.js // watch.js const observe = (obj, key, watchFun, deep, page) => { let oldVal = obj[key] // 如果监听对象是object类型并且指定deep(深度监听) if (oldVal !== null && typeof oldVal === 'object' && deep) { // 递归子集,依次执行observe() Object.keys(oldVal).forEach(item => { observe(oldVal, item, watchFun, deep, page) }) } // 使用Object.defineProperty()劫持数据的写操作,在监听对象改变后执行传入的watchFun Object.defineProperty(obj, key, { configurable: true, enumerable: true, set(value) { if (value === oldVal) return watchFun.call(page, value, oldVal) oldVal = value }, get() { return oldVal } }) } export const setWatcher = (page) => { // 页面里的data字段 const data = page.data // 页面里的watch字段 const watch = page.watch // 对watch里列举的每一个字段(需要监听的字段)执行observe() Object.keys(watch).forEach(key => { let targetData = data const targetKey = key // 支持deep深度监听,使用deep时需要配合handler使用,否则直接编写函数 const watchFun = watch[key].handler || watch[key] const deep = watch[key].deep observe(targetData, targetKey, watchFun, deep, page) }) } 二. 引入 使用 在需要使用监听机制页面的js文件的 [代码]onLoad[代码]钩子里,执行[代码]setWatcher[代码],并传入当前页面实例[代码]this[代码],完成初始化。 添加watch对象,内部写入需要被监听的字段以及执行函数: // test.js import { setWatcher } from '../../watch.js' Page({ data: { foo:false }, watch: { // 需要监听的字段 foo(val) { console.log('foo变化了,变化后的值是', val) // 具体操作=>doSomething } }, // watch初始化,传入当前页面实例this onLoad() { setWatcher(this) } }) 更多参考: https://www.cnblogs.com/520BigBear/p/16853678.html
2023-04-19 - 小程序app.globalData属性值改变时其它页面的引用响应更新
前提说明 小程序app.js的globalData中定义了userInfo属性,并且在首页和我的tab中引用了,当在其它页面更新userInfo后,在首页和我的中引用的userInfo未更新。 解决思路 利用发布-订阅的设计模式,app.js中的userInfo用Object.defineProperty实现数据劫持,当监听到userInfo值改变时,通知每个订阅者。在首页和我的页面调用app.js中的订阅方法,将更新数据的方法追加到userInfo的订阅者列表中。 实现代码 [代码]// app.js onLaunch: async function () { this.initObserve(); }, // 监听globalData中属性变化 initObserve() { const obj = this.globalData; const keys = ['userInfo']; keys.forEach(key => { let value = obj[key]; obj[`${key}SubscriberList`] = []; Object.defineProperty(obj, key, { configurable: true, enumerable: true, set(newValue) { obj[`${key}SubscriberList`].forEach(watch => { watch(newValue); }); value = newValue; }, get() { return value; } }); }); }, // 订阅globalData中某个属性变化 subscribe(key, watch) { watch(this.globalData[key]); this.globalData[`${key}SubscriberList`].push(watch); }, // 首页和我的page页 onLoad() { app.subscribe('userInfo', (userInfo) => { this.setData({ userInfo, }); }); }, [代码] 遇到的问题 小心Object.defineProperty中的set方法死循环导致栈溢出。在set用obj[key] = value时将会导致死循环,因为给属性赋值后,会再次调用set方法。解决的办法是利用闭包的原理,定义临时变量为obj[key],在set方法中对临时变量赋值。或者在obj中声明一个变量的副本,set中对变量副本赋值,get中返回变量副本。
2021-01-16 - app.onLaunch与page.onLoad异步问题
问题:相信很多人都遇到过这个问题,通常我们会在应用启动app.onLaunch() 去发起静默登录,同时我们需要在加载页面的时候,去调用一个需要登录态的后端 API 。由于两者都是异步,往往page.onload()调用API的时候,app.onLaunch() 内调用的静态登录过程还没有完成,从而导致请求失败。 解决方案:1. 通过回调函数// on app.js App({ onLaunch() { login() // 把hasLogin设置为 true .then(() => { this.globalData.hasLogin = true; if (this.checkLoginReadyCallback) { this.checkLoginReadyCallback(); } }) // 把hasLogin设置为 false .catch(() => { this.globalData.hasLogin = false; }); }, }); // on page.js Page({ onLoad() { if (getApp().globalData.hasLogin) { // 登录已完成 fn() // do something } else { getApp().checkLoginReadyCallback = () => { fn() } } }, }); ⚠️注意:这个方法有一定的缺陷(如果启动页中有多个组件需要判断登录情况,就会产生多个异步回调,过程冗余),不建议采用。 2. 通过Object.defineProperty监听globalData中的hasLogin值 // on app.js App({ onLaunch() { login() // 把hasLogin设置为 true .then(() => { this.globalData.hasLogin = true; }) // 把hasLogin设置为 false .catch(() => { this.globalData.hasLogin = false; }); }, // 监听hasLogin属性 watch: function (fn) { var obj = this.globalData Object.defineProperty(obj, 'hasLogin', { configurable: true, enumerable: true, set: function (value) { this._hasLogin = value; fn(value); }, get: function () { return this._hasLogin } }) }, }); // on page.js Page({ onLoad() { if (getApp().globalData.hasLogin) { // 登录已完成 fn() // do something } else { getApp().watch(() => fn()) } }, }); 3. 通过beautywe的状态机插件(项目中使用该方法) // on app.js import { BtApp } from '@beautywe/core/index.js'; import status from '@beautywe/plugin-status/index.js'; import event from '@beautywe/plugin-event/index.js'; const app = new BtApp({ onLaunch() { // 发起静默登录调用 login() // 把状态机设置为 success .then(() => this.status.get('login').success()) // 把状态机设置为 fail .catch(() => this.status.get('login').fail()); }, }); // status 插件依赖于 beautywe-plugin-event app.use(event()); // 使用 status 插件 app.use(status({ statuses: [ 'login' ], })); // 使用原生的 App 方法 App(app); // on page.js Page({ onLoad() { // must 里面会进行状态的判断,例如登录中就等待,登录成功就直接返回,登录失败抛出等。 getApp().status.get('login').must().then(() => { // 进行一些需要登录态的操作... }) }, }); 具体实现 具体实现可以参考我的商城小程序项目 项目体验地址:体验 代码:代码
2021-05-20