In this paper, starting from vivo Internet technology WeChat public links: https://mp.weixin.qq.com/s/Ka1pjJKuFwuVL8B-t7CwuA author: wu empty China research and development team

The background,

We believe that we have a better understanding of THE RSC through the technical disclosure of vivo how to build a Ten-million-level DAU campaign platform – Voyage. Remote Service Component (RSC) is the core component of an active page. It uses hot swap mechanism, visual configuration, plug and play to quickly build active pages.

RSC is an efficient abstract design scheme for active page components, which maximizes the development efficiency and reduces the mental burden of developers. We want developers to follow the design philosophy of “high cohesion, weak coupling” in their development, and only care about the internal presentation and logic processing of RSC components.

(figure 1)

But in the real business developing, we found that the above 1, we face every day a lot of similar scene, by the user to participate in the “monopoly” to get the points of the game, then you need to put the game monopoly components 】 【 results points to component 】 【 set card, and then obtain the corresponding component set card 】 【 card, click on the “double card”, Notify Monopoly Component to update the number of remaining games. There is a lot of collaboration and data sharing between components in this active page scenario. So if you think of an activity as a small front-end system, the RSC is only one of the basic elements that make up the system, and there is one very important element that cannot be ignored, which is the connectivity between the RSC components. Of course, this connection is also related to the context of the scene. Therefore, in the process of RSC component governance, the first need to solve is the management of data state between components in the active page.

Second, the results

Through the continuous in-depth thinking of the problem, explore the essence of the phenomenon behind the principle, from the architectural design level of a good solution to the component in the context of different scenarios (state management). Such as:

  • In the active page, we resolve the wiring between RSC components.

  • Within the platform, we solved the connection between the RSC components and the platform. Business RSC components need to be aware of key actions of the platform, such as activity saving, component deletion in the editor, and so on.

  • In the security sandbox within the editor, we addressed the connection between components and configuration panels across the sandbox.

Third, architecture evolution

Today we will focus on the RSC component to component connections in the active page. In the next article, we will talk about platform and sandbox RSC component connectivity.

Since we use Vue as our front-end UI infrastructure, the following technical solutions are based on Vue.

4. EventBus

(figure 2)

A picture is worth a thousand words, as shown in Figure 2. Of course, the simplest solution we have come up with is to implement a centralized event processing center to record the subscribers within the component, and to notify the subscribers within each related component through custom events when collaboration is needed. Of course, the notification can carry payload information for data sharing. In fact, Vue itself also has a custom event system, custom events between Vue components is based on this to achieve, detailed API please participate in the Vue documentation. We can implement the EventBus mechanism based on Vue itself without introducing new dependencies, reducing the bundle size, and using the API code shown below.

Const VM = new Vue() // Register subscriber VM$on('event-name', (payload) => {/* Performs the service logic */}) // Registers the subscriber.$once('some-event-name', (payload) => {/* Performs business logic */}) // Cancels a subscriber VM of an event.$off('event-name', [callback]) // Notify each subscriber to execute the corresponding business logic VM.$emit('event-name'The contentCopy the code


1. Architectural advantages

The advantages of data state management mode based on EventBus are found in practice:

  • The realization of the code is relatively simple, and the design scheme is easy to understand
  • Decoupling between components can be done in a lightweight manner, turning strong coupling between components into weak coupling to EventBus.

2. Pain points in practice

Of course, there are some disadvantages of the EventBus solution:

  • Because the business logic is scattered among multiple component subscribers, the processing of the business logic becomes fragmented and lacks a coherent context.
  • The need to constantly find subscribers in the code while reading and maintaining the code leads to disruption and distraction in business process understanding.

3. Reflect and improve

When realizing the architectural design deficiencies of EventBus, we also eat our Own Dog Food, realizing a set of visualization mechanism. By analyzing the abstract syntax tree of the code, we extract the information of subscribers and senders, and visually display the association between them, helping us to quickly understand the problem.

In addition, for complex business logic design [pre-script] improvement scheme. For example, if the active page is composed of multiple RSC components, but the requested server interface is still the same, containing all the data of the page initialization state, then we can unify the logic of fetching data in the pre-script, and then synchronize it to the RSC components. The prescript approach is to extract a global object containing shared state and business logic. Multiple components depend on this global object, and the architectural design, shown in Figure 3, complements the EventBus scheme.

(figure 3)


4, summarize

Pre-script can solve the problem that complex services are difficult to maintain and understand, but it also brings some risks, such as global objects that need to be exposed, which may be overwritten or modified. After the improvement of the pre-script, we have a clearer and clearer sense of what the state management mode we need is, that is, Vuex. So let’s talk about Vuex.

V. Vuex state management

1, the background

What is Vuex?

Vuex is a state management mode developed specifically for vue.js applications. It uses centralized storage to manage the state of all components of an application and rules to ensure that the state changes in a predictable way. Vuex is also integrated into Vue’s official debugging tool, DevTools Extension, which provides advanced debugging functions such as zero-configuration time-travel debugging, status snapshot import and export, and so on.

What are the features of Vuex?

  1. Centralized component state management with dynamic store registration
  2. The matching degree with Vue is high, and the bottom layer is realized based on the responsive data characteristics of Vue, maintaining the same data processing characteristics as Vue
  3. After getting familiar with Vue, you can quickly get started with Vuex, and the learning cost is relatively low
  4. Complete development experience, official DevTools and time-travel debugging help developers sort out predictable changes in data

2. Introduce support for Vuex on the platform

Vuex is a general purpose state management framework. How does it fit seamlessly into our RSC component architecture? We need to introduce Vuex dependencies and support in the project and store dependencies in the top-level Vue.

The basic structure of our project:

. └ ─ ─ the SRC ├ ─ ─ App. Vue ├ ─ ─ App. Less ├ ─ ─ assets ├ ─ ─ component ├ ─ ─ directive ├ ─ ─ the main, js ├ ─ ─stat├── ├─ ├─ ├─ ├─ package.json ├─ publicCopy the code


2.1 Adding a Dependency

According to the specification, we first add a Vuex dependency to package.json in our project directory

{
  "dependencies": {
    "vuex": "^ 3.0.1." "}}Copy the code


2.2 Creating a Store Object

//store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
exportConst store = new vuex.store ({// state datastate() {
    return {}
  },
  getters: {},
  mutations: {},
  actions: {},
})Copy the code


2.3 Top-level Vue objects are injected into the Store

Inject the create Store object above into the top-level Vue object so that all Vue components get the top-level store object through this.$store. Vuex also provides useful toolclass methods (mapState, mapActions, mapGetters, and mapMutations) for data sharing and collaboration.

// App.vue
import { store } from './store'New Vue({// inject store // In all Vue managed Vue objects can pass this.$storeTo get store,})Copy the code


3. Use Vuex to develop RSC components

3.1 RSC’s own Store

We still expect developers to focus on their presentation and business logic most of the time when developing components, and only share their state in the top-level store when components are rendered in the active page. So the component has its own independent store state management, module state isolation through the namespace, and then in the component beforeCreate life cycle method, through Vuex registerModule dynamic store registration.

3.2 StoreMixin injection

You can simplify this process by extracting common StoreMixins, and you can automatically turn on Namespaced: True and extend shortcuts and attributes for your current namespace. The code is as follows:

// store-mixn.js
export default function StoreMixin(ns, store) {
  return beforeCreateConst namespace = isFn(ns)? Const namespace = isFn(ns)? ns(this) : gen(ns) this.$ns = namespace
    store.namespaced = true
    this.$store.registerModule(Namespace, store) // Extend the shortcut and property this.$state = this.$store.state[namespace]
    this.$dispatch = (action, payload) =>
      this.$store.dispatch(`${namespace}/${action}`, payload)
    this.$getter= / /... this.$commit = //...
  }
}Copy the code


//store.js // The current component has its own storeexportDefault {// State of the component itselfstate() {
    return{} }, mutations: {}, getters: {}, //... Other things} // code.vue // External entry module import Store from components'./store'
export default {
  mixins: [StoreMixin(/*namespace*/ 'hello', /* component store */ store)],}Copy the code


3.3 How to Solve namespace Conflicts?

Because RSC components can be reloaded multiple times in an activity, so store modules with the same namespace can also be reloaded and overwritten. How do you guarantee the uniqueness of a namespace? We can, when registering a namespace in StoreMixin, determine if there is a namespace with the same name and rename the namespace if there is one. For example, if you register hello as a store in the command space, namspace hello will automatically become Hello1. The simple algorithm is implemented as follows,

/ / gen. Js / / generate a unique namespace const g = window | | global g. __namespaceCache__ = g. __namespaceCache__ | | {} / generate only * * * ModuleName * @param {*} name */export default function genUniqueNamespace(name) {
  let cache = g.__namespaceCache__
  if (cache[name]) {
    cache[name].count += 1
  } else {
    cache[name] = {
      count: 0,
    }
  }
  return name + (cache[name].count === 0 ? ' ' : cache[name].count)
}Copy the code


Alternatively, developers can pass custom functions in store-mixin to generate a unique namespace identity. For example, the following code sets a namespace based on the dynamic route parameters in vue-Router

export default {
  mixins: [StoreMixin((vm) => vm.$router.params.spuId), store],
}Copy the code


3.4 Challenges of dynamic namespaces

Because dynamic namespace will cause problems of uncertainty, the following code example, if Hello is renamed hello1, and when the mapXXX (mapState, mapMutations, etc.) method in Vuex, You need to pass the namespace exactly to get the context of the store within the component.

// code.vue
export default {
  mixins: [StoreMixin('hello', store)], computed: { ... mapGetters('hello', [ /* hello namespace store getter */ ]), ... mapState('hello', [ /* hello namespace state property */ ]), }, methods: { ... mapActions('hello', [ /* hello namespace actions method */ ]), ... mapMutations('hello', [
      /* hello namespace mutations method */
    ]),
  },
}Copy the code


3.5 Expanding Vuex to Support Dynamic namespaces

How to solve the problem of dynamic namespace in Vuex mapXXX method? The first thing we came up with was to set the namespace on Vue’s this.$ns object in StoreMixin, so that components mixed with StoreMixin can get namespace dynamically.

// store-mixn.js
export default function StoreMixin(ns, store) {
  return beforeCreateConst namespace = gen(ns) // Mount the renamed namespace to the current vUE object$nsThis attribute.$ns= namespace //... }}Copy the code


Although we can get the namespace of the store in the component via this.$ns, let’s assume we can:

// code.vue
exportdefault { computed: { ... mapGetter(this.$ns, [ /* hello namespace store getter */ ]), ... mapState(this.$ns, [ /* hello namespace state property */ ]), }, methods: { ... mapActions(this.$ns, [ /* hello namespace actions method */ ]), ... mapMutations(this.$ns, [
      /* hello namespace mutations method */
    ]),
  },
}Copy the code


Unfortunately, at this point this is not an instance of the current Vue at all, this.$ns undefined. So what to do? JS has many features of functional programming. Functions are also values and can be passed as parameters. In fact, in addition to values, functions also have lazy computed laziness, which is very important. Based on this thinking, mapXX method is extended to support dynamic namespace. Then in the mapXXX method, the namespace of the current component is retrieved only when the VM is a component instance of the current Vue.

// code.vue
import { mapGetters, mapState, mapActions, mapMutations } from 'vuex-helper-ext'

exportdefault { computed: { ... mapGetters((vm) => vm.$ns, [ /* hello namespace store getter */ ]), ... mapState((vm) => vm.$ns, [ /* hello namespace state property */ ]), }, methods: { ... mapActions((vm) => vm.$ns, [ /* hello namespace actions method */ ]), ... mapMutations((vm) => vm.$ns, [
      /* hello namespace mutations method */
    ]),
  },
}Copy the code


3.6 How do parent and child Components pass dynamic namespaces

I’m sure you’ve spotted one of the problems. This.$ns can only be retrieved from StoreMixin components. How to solve the problem that the child component gets the namespace of the parent component? At this time we need to use Vue powerful mixin system, design a global mixin, when the component is created to determine whether the parent component has $ns object, if there is, the current component $ns set to the parent component, if not, skip.

function injectNamespace(Vue) {
  Vue.mixin({
    beforeCreate: function _injectNamespace() {
      const popts = this.$options.parent;
      if (popts && popts.$ns) {
        this.$ns = popts.$ns;
        const namespace = this.$ns; // Extend the shortcut and property this for the component.$state = this.$store.state[namespace]
        this.$dispatch = (action, payload) =>
                            this.$store.dispatch(`${namespace}/${action}`, payload)
        this.$getter= / /... this.$commit= / /... }}}); } // main.js Vue.use(injectNamespace);Copy the code


With the magic of mixin, we can extend the design of the mapXXX method a little more elegently, because mapXX methods can use the $ns property as the default namespace. Be more refreshing and keep the official style, so as to better integrate Vuex into our system.

// code.vue
exportdefault { computed: { ... mapGetter([ /* hello namespace store getter */ ]), ... mapState([ /* hello namespace state property */ ]), }, methods: { ... mapActions([ /* hello namespace actions method */ ]), ... mapMutations([ /* hello namespace mutations method */ ]), }, }Copy the code


3.7 The last whole chestnut

We can see that for developers, just follow the standard Vuex development approach, as if nothing had happened. In fact, we have done a lot of efforts internally, and the purpose of architecture design is to [make simple things easier, make complex things possible].

Store. js The RSC component has its own store

export default {
  state() {
    return { mott: 'hello vue' }
  },
  mutations: {
    changeMott(state) {
      state.mott = 'hello vuex'}},}Copy the code


Text. Vue text subcomponent, mapState automatically dynamically retrieves the namespace

<template>
  <div @click="changeMott">{{ mott }}</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex-helper-ext'
exportdefault { computed: { ... mapState(['mott']), }, methods: { ... mapMutations(['changeMott']),
  },
}
</script>Copy the code


code.vue

<tempalte>
  <text></text>
</template>
<script>
import store from './store';
import text from './text';

export default {
  mixins: [StoreMixin('hello', store)],
  components: {
    text
  },
  methods: {
    // ....
  }
}
</script>Copy the code


Sixth, thinking and outlook

This article is written here, gradual end, thanks for the company. We reviewed the componentization scheme of RSC, the road we went through in solving the actual business scenarios of Wukong activity, and the team’s thinking on the management of state between RSC components in technical efforts. In the next article, we’ll talk about state management between RSC components and platforms and across sandbox environments.

For more content, please pay attention to vivo Internet technology wechat public account

Note: To reprint the article, please contact our wechat account: Labs2020.