wxminiprogram + proxy + global synchronized

Posted by czr on November 9, 2021

背景

由于小程序每个页面是由独立的webview来承载,所以当a页面数据更新,b页面无法自动同步到数据,只能手动到b页面重新去拉取,基于这个痛点,利用app.js的全局对象getApp()来做全局信息的订阅和发布,加上对page和component进行proxy,给指定钩子添加上事件监听和解绑

流程图

app.js + proxyGlobal

app.js: 实例化全局代理对象,添加注册和卸载入口

  App({
    ...
    // 全局信息,初始化
    globalData: {
      ...
    },
    // 实例化一个全局代理
    onLaunch () {
      this.proxyGlobal = new ProxyGlobal(this.globalData)
    },
    // 获取全局代理实例
    // 传入代理对象,则添加一个订阅者,返回最新的全局对象
    getAppGlobalData (page) {
      return this.proxyGlobal.getGlobalData(page)
    },
    // 组件或者页面卸载的时候要清空对应的监听器
    clearObserver (page) {
      this.proxyGlobal.clearObserver(page)
    },
  })

proxyGlobal.js: Observer订阅发布类 + ProxyGlobal代理类

Observer :用于发布和订阅消息

class Observer{
  constructor () {
    this.observers = []
  }
  add (observer) {
    if (this.observers.some(o => o === observer)) { return }
    this.observers.push(observer)
  }
  remove (observer) {
    this.observers.forEach((item, index) => {
      if (item === observer) {
        this.observers.splice(index, 1)
        return
      }
    })
  } 
  // 这里写死了updateGlobal为触发的消息回调,也可以写成add的时候添加指定的回调方法
  notify () {
    this.observers.forEach(observer => observer && observer.updateGlobal())
  }
}

ProxyGlobal :用于代理页面和组件,并添加订阅

class ProxyGlobal{
  constructor (globalData) {
    this.globalData = globalData
    this.observers = new Observer()
  }
  clearObserver (observer) {
    this.observers.remove(observer)
  }
  getGlobalData (observerPage) {
    observerPage && this.observers.add(observerPage)
    return this.globalData
  }
  setGlobalData (data) {
    this.globalData = data
    this.observers.notify()
  }
  static ProxyPage (originPage) {
    Reflect.set(originPage, 'updateGlobal', function () {
      console.log('global触发页面update', this)
      const pages = getCurrentPages()
      if (this === pages[pages.length - 1]) {
        const app = getApp()
        this.setData({ globalData: app.getAppGlobalData() })
      }
    })
    const proxyPage = new Proxy(originPage, {
      get (target, prop) {
        if (prop === 'onShow') {
          if (!target[prop]) {
            throw '代理页面必须添加onShow钩子,用于刷新数据'
          }
          Reflect.set(target, prop, new Proxy(target[prop], {
            apply (target, page, args) {
              // console.log('global触发页面onShow')
              const app = getApp()
              page.setData({ globalData: app.getAppGlobalData() })
              return Reflect.apply(target, page, args)
            }
          }))
        }
        if (prop === 'onLoad') {
          if (!target[prop]) {
            throw '代理页面必须添加onLoad钩子,用于添加监听'
          }
          Reflect.set(target, prop, new Proxy(target[prop], {
            apply (target, page, args) {
              // console.log('global触发页面onload')
              const app = getApp()
              page.setData({ globalData: app.getAppGlobalData(page) })
              return Reflect.apply(target, page, args)
            }
          }))
        }
        if (prop === 'onUnload') {
          if (!target[prop]) {
            throw '代理页面必须添加onUnload钩子,用于解绑监听'
          }
          Reflect.set(target, prop, new Proxy(target[prop], {
            apply (target, page, args) {
              // console.log('global触发页面onUnload')
              const app = getApp()
              app.clearObserver(page)
              return Reflect.apply(target, page, args)
            }
          }))
        }
        return Reflect.get(target, prop)
      }
    })
    return proxyPage
  }
  static ProxyComponent (originComponent) {
    Reflect.set(originComponent['methods'], 'updateGlobal', function () {
      // console.log('global触发组件update')
      const app = getApp()
      this.setData({ globalData: app.getAppGlobalData() })
    })
    const proxyComponent = new Proxy(originComponent, {
      get (target, prop) {
        if (prop === 'attached') {
          if (!target[prop]) {
            throw '代理组件必须添加attached参数,用于添加监听'
          }
          Reflect.set(target, prop, new Proxy(target[prop], {
            apply (target, component, args) {
              // console.log('global触发组件attached')
              const app = getApp()
              component.setData({ globalData: app.getAppGlobalData(component) })
              return Reflect.apply(target, component, args)
            }
          }))
        }
        if (prop === 'detached') {
          if (!target[prop]) {
            throw '代理组件必须添加detached参数,用于解绑监听'
          }
          Reflect.set(target, prop, new Proxy(target[prop], {
            apply (target, component, args) {
              // console.log('global触发组件detached')
              const app = getApp()
              app.clearObserver(component)
              return Reflect.apply(target, component, args)
            }
          }))
        }
        return Reflect.get(target, prop)
      }
    })
    return proxyComponent
  }
}

最后效果

所有数据由app.js缓存和更新,再通过proxy所有的page和component, 在组件和页面初始化的时候调用getAppGlobalData()来订阅全局信息的更新,再需要全局更新的时候,可以调用app.proxyGlobal.setGlobalData(newData),触发所有订阅者的数据更新,从而达到数据自动更新和同步


Creative Commons License
This work is licensed under a CC A-S 4.0 International License.