preface

In wechat applets, the globalData of app.js can be used as an intermediate bridge to transfer information between Page and Component, including Page to Page, Page to Component, and Component to Component. However, we were not aware of the changes under globalData in a timely way, and in the official default case of new applets, the network operation to get UserInfo was delayed and a lot of unnecessary code was written. This is true even in official cases, and I’m sure you’ll encounter similar situations in development. In this article, I’ll show you how to solve these problems.

Demand analysis

We believe that the following situations are common operations without global state management:

  1. In the Page, Component OnLoad, and Attached lifecycle hook functions, assign existing properties from the App’s globalData to the data in the Page or Component.

  2. At the very beginning, there are asynchronous network requests that fetch data for global use. It is possible that the globalData does not have the related attributes at the beginning, and the related attributes are not added to the globalData until the request is successful, at which point the Page assignment from the globalData may have been completed. This is just undefined, and requires further judgment before assigning to the Page, Component. If it’s just one or two and this is pretty easy, but multiple pages or multiple variables need to be assigned, I think you’re going to say no and look for ways to be lazy.

  3. Some of the variables assigned to pages and components from globalData are not only for judgment and presentation purposes, but we may need to change the value of the variables depending on user interaction, so the same variables in other pages and other components need to be changed uniformly.

In the above situation, we can put forward the following requirements:

  1. As soon as the page is initially loaded, get and assign the required properties from globalData to the page

  2. Pick up changes to a globalData property in a timely manner and perform subsequent actions

  3. When you change the Page/Component value, you do the same for other pages/components

Below is the raw code for the requirements

// app.js
App({
    globalData: {
        userInfo: null
    },
    onLaunch(){
        wx.getSetting({
            success: res= > {
                if(res.authSetting['scope.userInfo']){
                    wx.getUserInfo({
                        success: res= > {
                            this.globalData.userInfo = res.userInfo
                            2 / / demand
                            if (this.userInfoReadyCallback) {
                                // The presence of this callback means that the page executes onLoad
                                // Do not get userInfo and assign it to data of page
                                // Execute this callback function and assign the value to the corresponding page
                                this.userInfoReadyCallback(res)
                            }
                        }
                    })
                }
            }
        })
    }
})
Copy the code
// Pages/index/index.js
const app = getApp()
Page({
    // ...
    onLoad(options){
        1 / / demand
        const userInfo = app.globalData.userInfo
        userInfo && this.setData({useInfo})
        2 / / demand
        // If app.globalData.userinfo is not obtained
        The wx.getUserInfo callback has not been executed
        // Add a callback function to the app in response and bind this to the callback function
        userInfo || app.userInfoReadyCallback = res= > {
            this.setData({
                userInfo: res.userInfo
            })
            delete app.userInfoReadyCallback
        }
    }
})
Copy the code

This is the code for the official applet case, with only a few changes. It shows requirement 2 that the globalData property executes the page callback every time from scratch. It does not execute the callback every time.

We can think, the above requirements need to achieve, must have some code. As you can see, requirements 1 and 3 are primarily pages, component initialization, and globalData property changes that require the this.setData method, but each time this points to a different instance. Requirement 2 is that there should be a callback function, and that the callback function’s this should point to the corresponding instance, to be executed when the globalData property is changed.

In terms of time points, we have two, one is when the page, the component is initialized, and one is when the globalData property changes. So the first time point we can take into account the life cycle of the applet is the hook functions, onLoad and Attached, The this.setData operation is performed at both points in time. A globalData property change is an active or user event, which can be viewed as an event to a globalData property and then executed with some written callback function.

In terms of objects, this is basically the page and component instance this, and app.GlobalData.

Theoretical summary of demand

To sum up, we can automatically initialize this.setData (not manually), save this (to point to the corresponding instance when the event is executed), store the corresponding callback function as an event (the event is not executed function), and actively fire the event if necessary. As you can see, we need a variable across app, Page, and Component to hijack the initialized hook function, automate the assignment, store the corresponding event, and expose an event-triggered interface.

The paper come zhongjue shallow, and must know this to practice

See here, I believe you have a certain understanding of global state management, so exactly how to implement? Here, I want to emphasize that if you read this article and have some idea of what I’m talking about, you must try to implement the code yourself, good or bad, is always better than not implemented, you may learn more from your own implementation. Below above above case show a simple implementation code, did not see too clear an idea. Next time I will write a description of the code implementation, there should be.

// app.js
class Store {
  constructor(app){
    this['event'] = {}
    this.app = app
  }
  autoSet(globalData, instance){
    const instanceData = {}
    for (let prop of globalData){
      instanceData[prop] = this.app.globalData[prop]
      const callBack = (newValue) = > {
        instance.setData({[prop]: newValue})
        instance.watch[prop] && instance.watch[prop].call(instance, newValue)
      }
      this.addEvent(prop, callBack)
      instance.setData(instanceData)
      callBack(instanceData[prop])
      delete instance.watch
      delete instance.globalData
    }
  }
  addEvent(eventName, callBack){
    this.event[eventName] = this.event[eventName] || []
    this.event[eventName].push(callBack)
  }
  dispatch(eventName, newValue){
    this.app.globalData[eventName] = newValue
    this.event[eventName] && this.event[eventName].forEach(item= > item(newValue))
  }
}

App({
    globalData: {
        userInfo: null
    },
    onLaunch(){
        // New an instance and save it to the applet app for global calls
        this.store = new Store(this)
        wx.getSetting({
            success: res= > {
                if(res.authSetting['scope.userInfo']){
                    wx.getUserInfo({
                        success: res= > {
                            // The event is triggered when userInfo is obtained
                            this.store.dispatch('userInfo', res.userInfo)
                        }
                    })
                }
            }
        })
    }
})
Copy the code
// Pages/index/index.js
const app = getApp()
Page({
    // ...
    data: {
      userName: null
    },
    // The globalData array is used for automatic assignment
    globalData: ['userInfo'].// Listen on the corresponding globalData property and set the callback function
    watch: {
      userInfo(userInfo){
        console.log('userInfo updated '.this)
        this.setData({userName: userInfo.nickName})
      }
    },
    onLoad(options){
        // Pass in the globalData and the instance, set the data required by the instance, and create the event
        app.store.autoSet(this.globalData, this)
        // Anything else you want to do...}})Copy the code

The above code does not hijack the hook function, but instead executes the binding function at the start of the function, and does not free memory when the page is destroyed. There are a lot of things that can be optimized, but I’ll leave that to the next time.