1. Introduction

In many ways, wechat applet is similar to Vue and React. However, the applet itself does not provide a global state management tool like Vuex or Redux. App.globaldata is there, but it’s not responsive. In other words, changes to App.GlobalData do not drive pages, whereas data and properties in Page and Component do, so I wonder if I can somehow link App.GlobalData with data or properties.

Link the data in a particular Page or Component to the app.GlobalData value, and change the data value when the app.GlobalData data changes.

Just recently is learning MVVM source code implementation, see a very good post, thinking can this article ideas used in small procedures. This is portal, thanks to the author for sharing.

2. Scenario example

Suppose my applet has a page index, and the two components are childA and childB. The nested relationship between them is as follows:

We now want to define a data field indexData in index that is passed as a property value to child childA and child childB, and that value is displayed on the pages of index, childA, and childB. When indexData changes, all pages respond.

This function is simple and can be easily implemented by viewers who are familiar with value transfer between components.

// index.js
Page({
  data: {
    indexData: 'Default value'
  },
  onLoad: function () {
    setTimeout(() => {
        this.setData({
            indexData: 'Modified value'}, 3000)}})Copy the code
<! --index.wxml--> <view class="container">
  <childA class="childA" childaProp='{{indexData}}'></childA>
  <view class="usermotto">{{indexData}}</view>
</view>
Copy the code

We first declare the data field indexData in index.js, passing the childaProp property to component childA in the template.

// childa.js Component({/** * Component property list */ properties: {childaProp: {type: String,
      value: ' '}}})Copy the code
<! -- childa. WXML --> <view> I in childA: {{childaProp}}</view> <childB class="childB" childbProp='{{childaProp}}'></childB>
Copy the code

Declare the childaProp property in childa.js and pass the childbProp property of component B in the template.

// childb.js const app = getApp() Component({/** * list of Component properties */ properties: {childbProp: {type: String,
      value: ' '}}})Copy the code
<! -- childb.wxml --> <view> I in childB: {{childbProp}}Copy the code

This way, when index changes indexData after 3s, both childaProp and childbProp can respond and drive the page changes.

However, now that the requirements have changed, we need to change childbProp in the “grandchild” component childB, and hope that when childbProp changes, the values of his father and his grandfather will also change. Or you can expect values in unrelated sibling components (named childC, for example) to respond when the component childB changes.

This trigger is too sad, familiar with the global state management of the viewer can suddenly think: hey? Isn’t that when you need Vuex?

To quote from the Vuex overview on Vue website:

However, if you need to build a medium to large single-page application, and you’re probably thinking about how to better manage state outside of components, Vuex would be a natural choice. To quote Dan Abramov, the author of Redux, Flux architectures are like glasses: you know when you need them.

Comrades… This is the time, but wechat mini program does not have a Flux solution.

3. Secondary development of app.js

The following is the process of data hijacking through app.GlobalData and updating all subscriptions by publishing subscriptions. This is a complete reference to the MVVM principle mentioned above, except that the MVVM principle subscribes to all dom nodes that need data binding at template compilation time, whereas this is subscribing to all data or properties that need to be bound. In other words, dom changes when data changes; When app.globalData changes, the specified data changes with it.

The same thing as the same thing

The first is data hijacking

App({
  onLaunch: function() {/ /... This. Observe (this.globaldata.wxminix)}, observe:function (data) {
    let _this = this
    for (let key in data) {
      let val = data[key]
      this.observe(data[key])
      let dep = new Dep()
      Object.defineProperty(data, key, {
        configurable: true.get() {
          return val
        },
        set(newValue) {
          if (val === newValue) {
            return
          }
          console.log('newValue', newValue)
          val = newValue
          _this.observe(newValue)
        }
      })
    }
  },
  observe: function (data) {
    if(! data || typeof data ! = ='object') return   
    this.Observe(data)
  },
  globalData: {
    wxMinix: {
      indexData: ' '}}})Copy the code

Create a custom wxMinix property in the globalData object. We hijack the property to separate responsibilities. Other properties in globalData are not hijack and can still be used as normal app.globalData.

We iterate over each key in app.GlobalData.wxminix, overriding their accessor property with the Object.defineProperty method, and while the property value is still an Object, we repeat until each key is hijacked to. So when this property is evaluated or assigned, we pull two strings, and we do whatever we want to do at those two points in time.

At this point in any parts of the small program to app. GlobalData. WxMinix. IndexData assignment, can print out the console ‘newVlaue + the new value.

After that comes publishing subscriptions

The author of the original post explains what a publish subscription is, which is simply adding things to an array (callbacks) and publishing is executing them in turn. Here’s what we’re going to do: Subscription we want to talk to the app. GlobalData. WxMinix. IndexData binding data or properties, and then listening to the app. GlobalData. WxMinix. IndexData, he a change, Notify all subscribed data or properties immediately and ask them to follow suit.

// Define the observer and subscription list in app.js's global scopefunction Watcher(key, gd, fn) {
  this.key = key
  this.gd = gd
  this.fn = fn

  Dep.target = this
  let arr = key.split('. ')
  let val = this.gd
  arr.forEach(key => {
    val = val[key]
  })
  Dep.target = undefined
}

Watcher.prototype.update = function () {
  let arr = this.key.split('. ')
  let val = this.gd
  console.log(this.gd)
  arr.forEach(key => {
    val = val[key]
  })
  this.fn(val)
}

function Dep() {
  this.subs = []
}

Dep.prototype = {
  addSubs(watcher) {
    this.subs.push(watcher)
  },
  notify() {
    this.subs.forEach(watcher => {
      watcher.update()
    })
  }
}
Copy the code
// Modify the data hijacking code.function (data) {
    let _this = this
    for (let key in data) {
      let val = data[key]
      this.observe(data[key])
      letDep = new dep () // Instances of dep can be found insetDefineProperty (data, key, {64x: 64x, cx: 64x, cX: 64X, cX: 64X, cX: 64X, cX: 64X, cX: 64X)true.get() {dep.target && dep.addSubs(dep.target) // Subscribe to app.globalData.wxminixreturn val
        },
        set(newValue) {
          if (val === newValue) {
            return
          }
          console.log('newValue', newValue) val = newValue _this.observe(newValue) dep.notify() // Issue when app.globaldata.wxminix values change}})}}Copy the code

The main thing here is to maintain a Dep constructor with just one array property and two methods on its prototype chain, addSubs (subscribe) and notify (publish).

globalData: {
    wxMinix: {
      indexData: ' '
    }
},
makeWatcher: function (key, gb, fn) {
    new Watcher(key, gb, fn)
}
Copy the code

Finally, instantiate the observer and subscribe to it

Finally, we add a method to the app.js constructor to make it easy for external pages and components to call at any time. At this point our app.js functionality is complete, just need to call app.makeWatcher where you want.

. Such as: I hope that the index of js indexData and app globalData. WxMinix. IndexData binding, so I as long as in the index. The js onLoad life cycle to subscribe to the observer.

onLoad: function () {
    let _this = this
    app.makeWatcher('wxMinix.indexData', app.globalData, function(newValue) {
        _this.setData({
            indexData: newValue
        })
    })
  }
Copy the code

In this case, if you change the value of childbProp in childb.js:

lifetimes: {
    attached: function () {
      let_this = this // executes when the component instance enters the page node treesetTimeout(() => {
        app.globalData.wxMinix.indexData = 'Modified value from childB'
        console.log(app.globalData.indexData)
      }, 5000)
    },
    detached: function() {// execute when the component instance is removed from the page node tree},Copy the code

If you want to bind data to any component, simply subscribe to an observer in that page or component’s onLoad life cycle and change the value in the callback function.

Pay attention to

At this point, the development work is almost complete, but there are two points to note:

  • The data in app.globalData.wxminix that you want to monitor must be declared in a globalData object, because data hijacking in onLaunch will only hijack existing keys and pull their GET and set methods out of line. Although it looks like the data in app.GlobalData is not accessible in the applet without prior notification.)

  • If indexData is itself an object, it is possible to change only one of its attributes, but you need to declare a corresponding value in the specific page data or Properties, which can be changed during makeWatcher. Or can be modified in the app. GlobalData. WxMinix. IndexData, indexData deep copy to modify one of the attribute value, after the new indexData the whole assignment. Such as:

    let temp = JSON.parse(JSON.stringify(app.globalData.wxMinix.indexData))
    temp.name = 'Hahaha'
    app.globalData.wxMinix.indexData = temp
Copy the code

The shallow copy does not work � -_ – | |.

4. Afterword.

I feel so tired after writing for a long time. I hope I can help the readers in need. If there are loopholes or better methods, you are welcome to correct them. The widget’s built-in relations seems to do the same thing, but it’s too cumbersome. It still needs to be studied.

I feel that publishing subscription is written in a vague way. If you don’t understand it, you can direct the portal. It is very clear, and I will continue to modify this article.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — line — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

The above code for a modular package, and write about the use of documentation. Comrades can try it in their own small program ~ portal