用戶
 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

掃一掃,登錄網站

小程序社區 首頁 資訊/觀點 查看內容

使用觀察者模式+globalData實現小程序全局實時狀態管理

Rolan 2020-4-2 00:38

前言 在小程序的中,雖然有globalData這種全局唯一數據存儲,但是每個page并不能感知globalData是否發生了變化繼而去更新page的data。比如A頁面改變了globalData,但是B頁面并不能知道globalData改變了,B頁面只能 ...

前言

小程序的中,雖然有globalData這種全局唯一數據存儲,但是每個page并不能感知globalData是否發生了變化繼而去更新page的data。比如A頁面改變了globalData,但是B頁面并不能知道globalData改變了,B頁面只能在onShow這個生命周期中去重新獲取globalData,并setData

甚至在同一個頁面中,我改變了globalData,但頁面并不知道,必須手動去setData,這樣頁面中的數據才會重新渲染

于是我想是不是可以使用觀察者模式,讓globalData一改變,全部頁面就更新數據呢 當然,這個只是一個思路,并沒有考慮多次setData降低性能,你在看這篇博客時,可以當作是學習觀察者模式,哈哈,當然,如果能點個贊最好了,謝謝

二更,修bug。小程序每個頁面的js文件都是Page(對象),使用Reflect和Proxy對(對象)進行代理,使得只有路由棧在當前頁面才setData,并且減少耦合

什么是觀察者模式?

經評論區指正,改正了用詞,感謝

觀察者模式,有兩個角色,一個是目標,一個是觀察者。可以形象的比喻為老師和學生

老師在臺上講課(發布消息,觸發事件),學生在臺下聽課(接受消息)然后吸收知識(執行任務) 如果老師覺得你在開小差,他會讓你滾出教室(刪除觀察者)

老師覺得你反省的差不多了,他會讓你進入教室(增加觀察者)

class Teacher{
  constructor(){
    this.students=[];//用來存儲學生,即是存儲觀察者
  }
  add(student){
		this.students.push(studnet)
  }
  remove(student){
    this.students.forEach((item,index)=>{
      if(item==studnet){
        this.students.splice(index,1)
        return;
      }
    })
  }
  say(){//老師講課
    this.students.forEach(item=>{
      item.listen()//要求學生們聽課
    })
  }
}
class Student{
  listen(){
    console.log("我在聽課")
  }
}
復制代碼

總結:

目標有三個能力:

  1. 增加觀察者
  2. 刪除觀察者
  3. 通知觀察者執行某一個任務

觀察者有一個能力:

  1. 執行任務

在小程序中使用觀察者模式

小程序目錄如下

在publisher.js中

class Publisher {//發布者
  constructor() {
    this.observers = [];//存儲觀察者
  }
  add(observer) {//增加觀察者
    this.observers.push(observer);
  }
  remove(observer) {//刪除觀察者
    this.observers.forEach((item, index) => {
      if (item == observer) {
        this.observers.splice(index, 1);
        return;
      }
    });
  }
  notify() {// 向觀察者發布消息
    this.observers.forEach(item => {
      item.update();//  在每一個頁面中創建一個update函數用來更新globalData并渲染
    });
  }
}
// 這個類繼承Publisher并監聽globalData的變化
class GlobalDataPublisher extends Publisher {
  constructor(globalData) {
    super();
    this.globalData = globalData;
    this.observers = [];
  }
  getGlobalData() {
    return this.globalData;
  }
  setGlobalData(globalData) {// globalData一旦變化,就通知觀察者
    this.globalData = globalData;
    this.notify();
  }
}
module.exports = {
  Publisher,
  GlobalDataPublisher
};

復制代碼

app.js

//app.js
var { GlobalDataPublisher } = require('./utils/publisher');
App({
  onLaunch: function() {
    // 將這個類掛載到全局唯一實例的App上
    this.globalDataPublisher = new GlobalDataPublisher(this.globalData);
  },
  globalData: {
    userInfo: {
      name: '胡志武',
      age: 18,
      job: '前端攻城獅'
    }
  }
});

復制代碼

新建四個頁面

// 四個頁面全部一樣如下
// wxml
 <view>
    <text>name:{{userInfo.name}}</text>
    <text>age:{{userInfo.age}}</text>
    <text>job:{{userInfo.job}}</text>
</view>
<view bindtap="changeName">改名字為前端小學生</view>
<view bindtap="changeJob">改職位為前端打雜</view>
復制代碼
// js
// 這是每個頁面需要執行的任務,獲取globalData并setData,實現頁面及時渲染
update() {
  	console.log("user頁面更新")
    const app = getApp();
    const globalData = app.globalDataPublisher.getGlobalData();
    this.globalData = globalData;
    this.setData({
      ...globalData
    });
  },
復制代碼
//j s
// 每個頁面onLoad時。需要增加觀察者,
onLoad: function(options) {
    const app = getApp();
    app.globalDataPublisher.add(this);// 增加觀察者
    this.globalDataPublisher = app.globalDataPublisher;
    this.globalData = app.globalDataPublisher.getGlobalData();
    this.setData({
      ...this.globalData
    });
  },
復制代碼
// js 業務功能
// 用來檢查是不是全局實時更新
changeName() {
  	console.log("user頁面更新,changeName")
    this.globalData.userInfo.name = '前端小學生';
    this.globalDataPublisher.setGlobalData(this.globalData);
  },
  changeJob() {
    console.log("user頁面更新,changeJob")
    this.globalData.userInfo.job = '前端打雜';
    this.globalDataPublisher.setGlobalData(this.globalData);
  },
復制代碼

先點擊四個tabBar頁面實現注冊觀察者,然后點擊修改名字,會發現其他頁面也更新了globalData并setData

二更,使用Reflect和 Proxy對page進行代理

如果每個頁面都需要去添加注冊觀察者的代碼,和update函數就太麻煩了。

我們可以發現小程序每個頁面都是Page(對象), 我們可以對(對象)進行代理和反射,將我們需要的代碼加上去,

function createProxyPage(objProps){
  Reflect.set(objProps,"update",function(){
    ...代碼同上面的
  })
  return objProps
}
Page(createProxyPage{
     data:{}
		...
})
復制代碼

這樣就可以用createProxyPage讓每個頁面輕松添加上來update函數

那onLoad生命周期中的代碼我要怎么添加呢,還不能覆蓋原來的onLoad

function createProxyPage(objProxy) {
  Reflect.set(...)
  const proxyPage = new Proxy(objProxy, {
    get(target, prop) {
      if (prop === 'onLoad') {
        // 對onLoad函數進行代理
        Reflect.set(
          target,
          prop,
          new Proxy(target[prop], {
            //onLoad一執行,就會調用下面的代理
            apply(target, thisArgument, argumentsList) {
              //thisArgument就是調用onLoad函數的page對象
              const app = getApp();
              app.globalDataPublisher.add(thisArgument);
              thisArgument.globalDataPublisher = app.globalDataPublisher;
              thisArgument.globalData = app.globalDataPublisher.getGlobalData();
              thisArgument.setData({
                ...thisArgument.globalData
              });
              //執行完注冊觀察者代碼后,再去執行每個頁面onLoad中原有的代碼
              //即是不會覆蓋每個頁面的onLoad函數,onLoad函數照常執行
              return Reflect.apply(target, thisArgument, argumentsList);
            }
          })
        );
      }
    }
  });
}

復制代碼

完整代碼如下

function createProxyPage(objProps) {
  Reflect.set(objProps, 'update', function() {
    console.log('update更新數據');
    const pages = getCurrentPages();
    const page = pages[pages.length - 1];
    if (this === page) {
      const app = getApp();
      const globalData = app.globalDataPublisher.getGlobalData();
      this.globalData = globalData;
      this.setData({
        ...globalData
      });
    }
  });
  const proxyPage = new Proxy(
    objProps,

    {
      get(target, prop) {
        if (prop === 'onShow') {
          Reflect.set(
            target,
            prop,
            new Proxy(target[prop], {
              apply(target, thisArgument, argumentsList) {
                const app = getApp();
                thisArgument.globalData = app.globalDataPublisher.getGlobalData();
                thisArgument.setData({
                  ...thisArgument.globalData
                });
                console.log('onShow更新數據');
                return Reflect.apply(target, thisArgument, argumentsList);
              }
            })
          );
          if (prop === 'onLoad') {
            Reflect.set(
              target,
              prop,
              new Proxy(target[prop], {
                apply(target, thisArgument, argumentsList) {
                  const app = getApp();
                  app.globalDataPublisher.add(thisArgument);
                  thisArgument.globalDataPublisher = app.globalDataPublisher;
                  thisArgument.globalData = app.globalDataPublisher.getGlobalData();
                  thisArgument.setData({
                    ...thisArgument.globalData
                  });
                  return Reflect.apply(target, thisArgument, argumentsList);
                }
              })
            );
          }
        }
        return Reflect.get(target, prop);
      }
    }
  );
  return proxyPage;
}
module.exports.createProxyPage = createProxyPage;

復制代碼

具體代碼在下面鏈接中:

github.com/huzhiwu1/gl…

結語

作者:胡志武

時間:2020/03/26 二更:2020/03/28 如果有錯漏處,請指正。看官們點個贊吧


鮮花
鮮花
雞蛋
雞蛋
分享至 : QQ空間
收藏
原作者: 胡志武98 來自: 掘金
河北20选5大星走势图