1. Introduction
In the H5 Vue project, the most common one is a single page application (SPA), using Vue-Router to control the mounting and multiplexing of components. At this time, using Vuex can easily maintain the data state without having to care about data communication between components. However, in Weex, different execution environments are used between different pages, and data cannot be shared. At this time, data communication is mostly achieved through BroadcastChannel or storage modules. This article mainly uses a decorator to extend the functions of Vuex, realize data storage in modules, and reduce the coupling degree with business code.
2、Decorator
There is a decorator pattern in the design pattern that extends the functionality of an object at runtime without creating multiple inherited objects. Similarly, Decorator can extend the function of an object at compile time, reducing the degree of code coupling while achieving the same effect of multiple inheritance.
2.1. Decorator installation
At present, Decorator is only a proposal and cannot be used directly in production environments. It can be implemented using babel-plugin-transform-decorators-legacy. Using npm to manage dependency packages can execute the following commands:
npm install babel-plugin-transform-decorators-legacy -D
Then configure it in .babelrc
{ "plugins": [ "transform-decorators-legacy" ] }
Or configure it in
{ test: /\.js$/, loader: "babel-loader", options: [ plugins: [ require("babel-plugin-transform-decorators-legacy").default ] ] }
At this time, you can write the Decorator function in the code.
2.2. Writing Decorator
In this article, Decorator mainly modifies the method, and the main code is as follows:
const actionDecorator = (target, name, descriptor) => { const fn = ; = function(...args) { ('The decorator method was called'); return (this, args); }; return descriptor; };
const module = { state: () => ({}), actions: { @actionDecorator someAction() {/** Business Code **/ }, }, };
It can be seen that the three parameters of the actionDecorator modifier are the same. By modifying the function, the someAction method is rewrited at compile time. When calling the method, it will first execute ('The decorator method is called'); and then call the business code in the method. For the implementation of multiple functions, such as storing data, sending broadcasts, printing logs and data burying points, adding multiple Decorators.
3、Vuex
Vuex itself can subscribe to the corresponding mutation and action with subscribeAction, but only supports synchronous execution. Weex's storage storage is asynchronous, so the existing methods of Vuex need to be extended to meet the corresponding needs.
3.1. Modification action
In Vuex, state can be changed through commit mutation or dispatch action, and the essence of action is to call commit mutation. Because storage contains asynchronous operations, we choose to modify the action to extend the function without breaking the Vuex code specification.
Storage uses callback functions to read and write items. First we encapsulate them into a Promise structure:
const storage = ('storage'); const handler = { get: function(target, prop) { const fn = target[prop]; // Only these two methods are needed here if ([ 'getItem', 'setItem' ].some(method => method === prop)) { return function(...args) { // Remove the callback function and return promise const [callback] = (-1); const innerArgs = typeof callback === 'function' ? (0, -1) : args; return new Promise((resolve, reject) => { (target, ...innerArgs, ({result, data}) => { if (result === 'success') { return resolve(data); } // Prevent errors from being reported by module without saving state return resolve(result); }) }) } } return fn; }, }; export default new Proxy(storage, handler);
Through Proxy, setItem and getItem are encapsulated as promise objects, and excessive callback structures can be avoided during subsequent use.
Now we write the storage setItem method to the modifier:
import storage from './storage'; // Add a rootKey to prevent rootState's namespace from causing an error// Can be replaced with other strings by itselfimport {rootKey} from './constant'; const setState = (target, name, descriptor) => { const fn = ; = function(...args) { const [{state, commit}] = args; // action is an asynchronous operation, return promise, // And the state needs to be stored in the storage when the state is modified to fulfilled return (this, args).then(async data => { // Get the moduleMap of the store const rawModule = (this._modulesNamespaceMap); // According to the current commit, find the module where this action is located const moduleMap = (([, module]) => { return === commit; }); if (moduleMap) { const [key, {_children}] = moduleMap; const childrenKeys = (_children); // Only get the state of the current module, and hand it over to store the state of the childModule. Press the module to store the data to avoid excessive storage of data // Can be used to polyfill, or reduce can be used instead const pureState = ((state).filter(([stateKey]) => { return !(childKey => childKey === stateKey); })); await (rootKey + key, (pureState)); } // Pass data backward along the promise chain return data; }); }; return descriptor; }; export default setState;
After completing the setState modifier function, you can decorate the action method. In this way, after the promise status returned by the action is modified to fulfilled, the storage storage function is called, and the data status is saved in time to load the latest data on the newly opened Weex page.
import setState from './decorator'; const module = { state: () => ({}), actions: { @setState someAction() {/** Business Code **/ }, }, };
3.2. Read module data
After storing data to storage, we also need to automatically read data and initialize the Vuex status in the newly opened Weex page instance. Here we use Vuex's plugins settings to complete this function.
First, let's write Vuex plugin:
import storage from './storage'; import {rootKey} from './constant'; const parseJSON = (str) => { try { return str ? (str) : undefined; } catch(e) {} return undefined; }; const getState = (store) => { const getStateData = async function getModuleState(module, path = []) { const {_children} = module; // Read the data stored in the storage under the current module according to the path const data = parseJSON(await (`${('/')}/`)) || {}; const children = (_children); if (!) { return data; } // Remove the childModule's data and read it recursively const childModules = await ( (async ([childKey, child]) => { return [childKey, await getModuleState(child, (childKey))]; }) ); return { ...data, ...(childModules), } }; // Read local data, merge to Vuex state const init = getStateData(store._modules.root, [rootKey]).then(savedState => { (merge(, savedState, { arrayMerge: function (store, saved) { return saved }, clone: false, })); }); }; export default getState;
The above completes the reading of Vuex data according to module, but the storage storage in Weex's IOS/Andriod is asynchronous. In order to prevent the data returned by sending the request after the component is mounted, it is necessary to read the local data and merge it to state before calling new Vue. Here we use a simple interceptor to intercept:
const interceptors = {}; export const registerInterceptor = (type, fn) => { const interceptor = interceptors[type] || (interceptors[type] = []); (fn); }; export const runInterceptor = async (type) => { const task = interceptors[type] || []; return (task); };
In this way, getState is modified to:
import {registerInterceptor} from './interceptor'; const getState = (store) => { /** other code **/ const init = getStateData(store._modules.root, []).then(savedState => { (merge(, savedState, { arrayMerge: function (store, saved) { return saved }, clone: false, })); }); // Put promise into the interceptor registerInterceptor('start', init); };
import getState from './plugin'; import setState from './decorator'; const rootModule = { state: {}, actions: { @setState someAction() {/** Business Code **/ }, }, plugins: [getState], modules: { /** children module**/ } };
import {runInterceptor} from './interceptor'; // All promises in the interceptor are returned to resolved before instantiating the Vue root component// You can also use Vue-Router's global guard to complete itrunInterceptor('start').then(() => { new Vue({/** other code **/}); });
This implements the instantiation of the Weex page, first read the storage data to Vuex's state, then instantiate the components of each Vue, and update their respective module states.
4、TODO
Through Decorator, Vuex's data sub-module is stored in storage, and when the Store is instantiated, data is read through the plugin module and then merged to state, improving data storage efficiency while achieving decoupling from business logic code. But there are still some optimization points:
1. Triggering action will all states in all modules. Just save the required state to avoid storing useless data.
2. For modules registered with registerModule, it is necessary to support automatic reading of local data.
3. The module with namespaced is false cannot be obtained through _modulesNamespaceMap, and it needs to be changed to traversal _children.
This will not be expanded here and will be implemented in subsequent versions.
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.