Native applets state management: MPSM

Have experience in vue+ React development

It integrates [VUE’s attribute monitoring watch, computed attribute computed] and [DVAJS’s global state management mode] to solve the problem of cross-page communication, and puts forward the concept of “component circle” to get rid of the communication paste between various components, such as father and son, brother and sister, neighbor and distant cousin.

The data flow

Global state or circle state (local) can be easily dispach, not only between pages, but also between components and within nested components.

Global data flow

Circular data stream

Used to introduce

Fully compatible with native code, existing business logic code, even if not suitable can also use this library, does not affect the existing business logic.

Native: Page() becomes Page() ()

Native: Component() to Component() ()

Initialize the

//app.js

import {page} from './mpsm/index'
import models from './models/index'

page.init(models, {}, {})

Copy the code

The second and third parameters are the public options for the page and component, respectively. These parameters are valid for each page or component. Objects and functions with the same name are merged.

Page or component registration

Small: just lowercase the first letter of Page and Component.

Tail: that is, tail is called once more:

page({ //component
	// ...}) ()Copy the code
// There are two ways to update data
// First, modify this.data directly, and call this.update() when needed.
this.data.a = 1
this.update()

// This.$setData
this. $setData({
  list: list
})

// The first way to update data is to call this.update() as needed.
// The second update mode will be batch updated after the function is executed. The asynchronous operation will be updated immediately, similar to setState in React


Copy the code

methods

methods instructions note
page For the registration page page()()
component Used to register components component()()
dispatch State distribution Parameter object, {type, action, lazy}, default lazy: true, lazy update, indicating that only the status of the current page is updated, other pages will be updated after onShow trigger
getComponentOps Gets the common component configuration options No functions, just simple JSON-formatted data
getOps Gets public page configuration options No functions, just simple JSON-formatted data
subscribe Subscribing to a single data source ‘the userInfo/setup’ parameters.
unsubscribe Unsubscribe from a single data source ‘the userInfo/setup’ parameters.
Unsubscribe from a single data source

Method: unsubscribe, subscribe

Unsubscribe does not use dVA app.unmodel(), and subscription must return the unlisten method used to unsubscribe data.

subscribe('userInfo/setup');
unsubscribe('userInfo/setup');
Copy the code

Page registration, component registration example

import {dispatch, page, component} from '.. /.. /mpsm/index'

page({ / / or component
  watch: { // Attribute listener
    isLogin(newState, oldState) {
    }
  },
  computed: { // Calculate attributes
    countComputed(data) {
      return data.count * 2}},data: {
    count: 2
  },
  onLoad() {},
  login() {
    dispatch({
      type: 'userInfo/save'.payload: {
        isLogin: true
      }
    })
  },
  changeGroupState() {
    this.dispatch({
      type: 'group/index-a-1'.payload: {
        nameA: 'name'}})}}) (({userInfo}) = > {// Subscribe to global status
  return {
    isLogin: userInfo.isLogin
  }
}, (groups) => {// Subscribe to the circle state,page is the global circle, component is the circle of the component
  return {
    nameA: groups.nameA && groups.nameA.name || The '-'}})Copy the code
tips:
  1. Dispatch dispatches global status (dVA) in the same style as DVA, but with a lazy field (true) or a full update (false).

  2. Page and Component instances have a built-in this.dispatch method to distribute local state.

3, the component can listen to the page life cycle function, do not need to do version compatibility, just want to listen to the function name and page, that is

// Native pageLifetimes can listen for too few cycles, and version requirements are high, listen onShow, do not need to listen for show, avoid executing twice
component({
  pageLifetimes: {
      onShow: function () {},onHide: function () {},onPageScroll: function () {},}}) ()Copy the code

Current listening lifecycle [‘onShow’, ‘onHide’, ‘onResize’, ‘onPageScroll’, ‘onTabItemTap’, ‘onPullDownRefresh’, ‘onReachBottom’]

With this function, you can write many automatic control components, such as the top effect of the navigation bar, etc.

Models Global status

Reference dvajs

export default {
  namespace: ' '.state: {},subscriptions: {},effects: {},reducers: {}}Copy the code

The model method

See DVA for details

Select, put, history
tips:
  1. Put is only used for simple action distribution within the model. It does not encapsulate methods such as PUT. Resolve, which can be achieved with async/await.
  2. According to the applets feature, the two parameters in the history callback are the routing information of the current page and the routing information of the previous page, which are an object in the format of.

export default {
  subscriptions: {
    setup({dispatch, history, select}) {
      const callback = (current, prev) = > {
              dispatch({
                type: 'save'.payload: {
                  prev, // {route: 'pages/index/index',options: {id: 123}}
                  current
                }
              })
            }
      history.listen(callback)
      return (a)= > history.unlisten(callback)
    
    }
  }
}

Copy the code

Component circle

Traditional component data flow

For the data communication between the nested components, there are often called father and son relationship between components, the inner components to outer components or on other branches of transfer data component, often through the outer components through monitoring function to receive and distribute, layer upon layer, this mode of this layer upon layer transfer, cumbersome and need special function to maintain, is not conducive to the component of expansion and transplantation.

Traditional component data flow

The official component data flow of the applets

For native component systems of small programs, behaviors, relations, definitionFilter, triggerEvent, getRelationNodes and other attributes or methods that need to be used to write components are really hot eyes! Small program official component system basically lost the meaning as a component, although it is still trying to update the upgrade, from time to time in the document appears “do not recommend the use of this field, but use another field instead, it is more powerful and better performance” words, we do not believe!!

Can’t connect

Applets component data flow

What is a component? Relatively independent, with clearly agreed interface, dependent context, can be freely combined, can be expanded, can be deployed independently, can also be used in combination!! You say you apart from relative independence, you say you have what, you say you have what!!

what

Component circle data flow

For a component, the original writing, should not have to care about oneself is mounted to which, you only need to provide data for circles, even the data key name also need not care about, in a circle, who love who, without the callback function to help pass data, the significance of this is as a portable, reusable components, in a circle, there is no concept of father and brother, No component is born to be a son, and the communication between components is purely data communication.

In the data communication between component C and component F in the figure below, one provides data and the other directly uses it. No matter how many layers are involved, the communication is direct without unnecessary steps.

Circular data stream

    <! -- component a in page-->
    <a group-name="index-a-1" group-keys="{{ {name: 'nameA1'} }" group-data="{{ {a: 1} }}"></a>
Copy the code

If you assign a group-name attribute to a component, the component is assigned a circle. The payload at the this.dipatch({}) distribution site of the component is the data that the component contributes to the circle.

Group-keys are used to avoid field name collisions. In this example, we can change the value of a’s contribution data key to nameA1, but the value remains the same. Therefore, the local component in the circle is pure and only provides the necessary data values to the circle.

Group-data is an external value that the component depends on and is a built-in property that can be listened on in the component’s Watch configuration.

Page, component instance object

Note: The functions in the configuration and setData are simply encapsulated. In the function of page registration or component registration of options, setData data will not be updated immediately, but collected in a combination. After the current function is executed, computed data is simulated, combined with the collected results, and then diff is performed. SetData one last time. The React update mechanism is similar to that of the react update mechanism. For asynchronous operations in functions, data is not collected. Instead, it directly simulates calculated values, diff, and setData

page

Has the highest authority to manage the state of the circle

attribute

attribute instructions note
$groups Gets the status values of all circles within the current page Object, read-only
    this.$groups['index-a-1'] 
Copy the code

methods

dispatch

Used to force a state update in a circle

    this.dispatch({
      type: 'data/index-a-1'.payload: {
        nameA1: 'name'}})Copy the code
parameter instructions note
type Format: this.setdata (payload) This. SetData (payload) string
payload Status value that needs to be updated object

component

Only the status of the circle is updated

attribute

attribute instructions note
$groups Gets the status values of all circles on the page Object, read-only
$group Gets the status of the circle Object, read-only
    this.$group['nameA1'] 
Copy the code

methods

dispatch

Used to update the status of the circle

    this.dispatch({
      type: 'data'.// group
      payload: {
        nameA1: 'name'}})Copy the code
parameter instructions note
type Format: this. SetData (payload); this. SetData (payload) string
payload Status value that needs to be updated object

tips

Update the type

The reason for the data update type is that the name of the data provided by the component can be overwritten, so the component should not listen for its own data, that is for outsiders to use.

Sometimes a component contributes data to a circle but does not need that data to be updated to data itself, so there is a group update type

Status update mechanism

Status update mechanism

Customize the diff

It will reset the deleted attribute value to NULL. I do not need null. When traversing the object’s attribute value, there will be an extra null attribute. Delete is delete, do not need to keep, and the internal implementation, before diff to do a synchronization key, this is not necessary, waste performance. Westore diff should be used for new and old global states. The result violates setData’s data splice rules and does not conform to the custom of updating native data. Therefore, I have to write a DIFF by myself, first listing the basic situation:

1. Do not change the usage habits of native setData;

2. Specify the scenarios where DIFF is used and customize for specific scenarios: DiFF is required for attribute update and DIFF is required for setData;

3. The format of the result returned by diff will be used by setData, which needs to meet the format of a.b[0]. A.

4. The two parameters involved in comparison have a common feature, that is, they compare the full amount of old data with locally changed data. For attributes, the key-value pairs of the two are even equivalent, so there is no need to synchronize the key-value.

Custom diff:

   diff({
       a: 1.b: 2.c: "str".d: { e: [2, { a: 4 }, 5]},f: true.h: [1].g: { a: [1.2].j: 111}}, {a: [].b: "aa".c: 3.d: { e: [3, { a: 3}},f: false.h: [1.2].g: { a: [1.1.1].i: "delete" }, k: 'del'
   })
Copy the code

Results the diff

   const diffResult = {
    result: {
      a: 1.b: 2.c: "str".'d.e[0]': 2.'d.e[1].a': 4.'d.e[2]': 5.f: true.g: {
        a: [1.2].j: 111
      },
      h: [1]},rootKeys: {
      a: true.b: true.c: true.d: true.g: true.h: true,}}Copy the code

RootKeys are recorded because attribute listening does not require a format like ‘a.b’, and recording it in diff saves one traversal filter split.