波多野吉不卡中文Av无码Av

      <strong id="fzzds"><track id="fzzds"></track></strong>
    1. <strong id="fzzds"></strong>
      <strong id="fzzds"></strong>
    2. <ruby id="fzzds"><bdo id="fzzds"><rp id="fzzds"></rp></bdo></ruby>
        <strike id="fzzds"></strike>
      1. 您現在的位置: 微信小程序 > 微信小程序運營 > 經驗 >

        你可以零侵入式實現小程序的全局狀態管理嗎

        來源:微信小程序 編輯:Yiyongtong.com 發布時間:2020-05-18 11:05熱度:

        哈嘍,今天我們聊聊小程序的狀態管理~(有這玩意嗎)

        我們要實現什么

        很簡單,實現一個全局響應式的globalData,任何地方修改=>全局對應視圖數據自動更新。

        并且我希望在此過程中盡量不去change原有的代碼邏輯。

        為啥要實現

        寫過小程序的都知道,狀態管理一直是小程序的一大痛點。

        由于小程序官方沒有一個全局狀態管理機制,想要使用全局變量只能在app.js里調用App()創建一個應用程序實例,然后添加globalData屬性。但是,這個globalData并不是響應式的,也就是說在某個頁面中修改了其某個值(如果初始化注入到data中)無法完成視圖更新,更別說全局頁面和組件實例的更新了。

        當前的主流做法

        我們先來了解下當下比較流行的方案。

        我們以westore為例,這是鵝廠出的一款覆蓋狀態管理、跨頁通訊等功能的解決方案,主要流程是通過自維護一個store(類似vuex)組件,每當頁面或組件初始化時注入并收集頁面依賴,在合適的時候手動update實現全局數據更新。提供的api也很簡潔,但是如果使用的話需要對項目原有代碼做一些侵入式的改變。比如說一:創建頁面或組件時只能通過該框架的api完成。二:每次改變全局對象時都要顯式的調用this.update()以更新視圖。

        其他一些方案也都是類似的做法。但我實在不想重構原項目(其實就是懶),于是走上了造輪子的不歸路。

        準備工作

        正式開始前,我們先理一下思路。我們希望實現

        1. 將globalData響應式化。
        2. 收集每個頁面和組件data和globalData中對應的屬性和更新視圖的方法。
        3. 修改globalData時通知所有收集的頁面和組件更新視圖。

        其中會涉及到發布訂閱模式,這塊不太記得的可以看看我之前的文章喲。

        Talk is cheap. Show me the code.

        說了這么多,也該動動手了。

        首先,我們定義一個調度中心Observer用來收集全局頁面組件的實例依賴,以便有數據更新時去通知更新。 但這里有個問題,收集整個頁面組件實例未免太浪費內存且影響初始化渲染(下面的obj),如何優化呢?

        // 1.Observer.js
        export default class Observer {
          constructor() {
            this.subscribers = {};
          }
        
          add (key, obj) { // 添加依賴 這里存放的obj應該具有哪些東東?
            if (!this.subscribers[key]) this.subscribers[key] = [];
            this.subscribers[key].push(obj);
          }
        
          delete () { // 刪除依賴
            // this.subscribers...
          }
        
          notify(key, value) { // 通知更新
            this.subscribers[key].forEach(item => {
              if (item.update && typeof item.update === 'function') item.update(key, value);
            });
          }
        }
        
        Observer.globalDataObserver = new Observer(); // 利用靜態屬性創建實例(相當于全局唯一變量)
        復制代碼

        相信很多同學想到了,其實我們只需要收集到頁面組件中data和更新方法(setData)就夠了,想到這里,不妨自定義一個Watcher類(上面的obj),每次頁面組件初始化時new Watcher(),并傳入需要的數據和方法,那我們先完成初始化注入的部分。

        // 2.patcherWatcher.js
        // 相當于mixin了Page和Component的一些生命周期方法
        import Watcher from './Watcher';
        function noop() {}
        
        const prePage = Page;
        Page = function() {
          const obj = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
          const _onLoad = obj.onLoad || noop;
          const _onUnload = obj.onUnload || noop;
        
          obj.onLoad = function () {
            const updateMethod = this.setState || this.setData; // setState可以認為是diff后的setData
            const data = obj.data || {};
            // 頁面初始化添加watcher 傳入方法時別忘了綁定this指向
            this._watcher = this._watcher || new Watcher(data, updateMethod.bind(this));
            return _onLoad.apply(this, arguments);
          };
          obj.onUnload = function () {
            // 頁面銷毀時移除watcher
            this._watcher.removeObserver();
            return _onUnload.apply(this, arguments);
          };
          return prePage(obj);
        };
        // 。。。下面省略了Component的寫法,基本上和Page差不多
        復制代碼

        接著,根據我們的計劃,完成Watcher的部分。這里會對傳入的data做層過濾,我們只需要和globalData對應的屬性(reactiveData),并在初始化時注入Observer。

        // 3.Watcher.js
        import Observer from './Observer';
        const observer = Observer.globalDataObserver;
        let uid = 0; // 記錄唯一ID
        
        export default class Watcher {
          constructor() {
            const argsData = arguments[0] ? arguments[0] : {};
            this.$data = JSON.parse(JSON.stringify(argsData));
            this.updateFn = arguments[1] ? arguments[1] : {};
            this.id = ++uid;
            this.reactiveData = {}; // 頁面data和globalData的交集
            this.init();
          }
        
          init() {
            this.initReactiveData();
            this.createObserver();
          }
        
          initReactiveData() { // 初始化reactiveData
            const props = Object.keys(this.$data);
            for(let i = 0; i < props.length; i++) {
              const prop = props[i];
              if (prop in globalData) {
                this.reactiveData[prop] = getApp().globalData[prop];
                this.update(prop, getApp().globalData[prop]); // 首次觸發更新
              }
            }
          }
        
          createObserver() { // 添加訂閱
            Object.keys(this.reactiveData) props.forEach(prop => {
              observer.add(prop, this);
            });
          }
        
          update(key, value) { // 定義observer收集的依賴中的update方法
            if (typeof this.updateFn === 'function') this.updateFn({ [key]: value });
          }
        
          removeObserver() { // 移除訂閱 通過唯一id
            observer.delete(Object.keys(this.reactiveData), this.id);
          }
        }
        復制代碼

        最后,利用Proxy完成一個通用的響應式化對象的方法。

        這里有個小細節,更改數組時set會觸發length等一些額外的記錄,這里就不細說了,有興趣的同學可以了解尤大在vue3.0的是如何處理的(避免多次 trigger)。

        // 4.reactive.js
        import Observer from './Observer';
        const isObject = val => val !== null && typeof val === 'object';
        
        function reactive(target) {
          const handler = {
            get: function(target, key) {
              const res = Reflect.get(target, key);
              return isObject(res) ? reactive(res) : res; // 深層遍歷
            },
            set: function(target, key, value) {
              if (target[key] === value) return true;
              trigger(key, value);
              return Reflect.set(target, key, value);
            }
          };
          const observed = new Proxy(target, handler);
          return observed;
        }
        
        function trigger(key, value) { // 有更改記錄時觸發更新 => 會調用所有Watcher中update方法
          Observer.globalDataObserver.notify(key, value);
        }
        
        export { reactive };
        復制代碼

        最后的最后,在app.js引用就好啦。

        // app.js
        require('./utils/patchWatcher');
        const { reactive } = require('./utils/Reactive');
        
        App({
          onLaunch: function (e) {
            this.globalData = reactive(this.globalData); // globalData響應式化
            // ...
          },
          // ...
          globalData: { /*...*/ }
        復制代碼

        總結

        綜上,我們一步一步從 頁面組件初始化注入=>定義Watcher類=>將Watcher收集到Observer中 并在此觸發更新=>app.js全局引入 這幾個步驟完成globalData的響應式化,結果是通過新增4個文件 app.js3行代碼(包括注釋等共100多行代碼),幾乎以零侵入的方式完成,并且實現了功能分離,具有一定的可擴展性。

        時間倉促,文中肯定會有一些不夠嚴謹的地方,歡迎大家指正和討論。

        感謝閱讀的你!

         
        波多野吉不卡中文Av无码Av

          <strong id="fzzds"><track id="fzzds"></track></strong>
        1. <strong id="fzzds"></strong>
          <strong id="fzzds"></strong>
        2. <ruby id="fzzds"><bdo id="fzzds"><rp id="fzzds"></rp></bdo></ruby>
            <strike id="fzzds"></strike>