First, why

  1. Vuex makes code cumbersome (too much code) and “official instructions ignored,” and most of the time we don’t use state tracking, just getters, setters, mutations; Does Vuex really make it easier to read code when there are many states?

  2. It’s bad for editors and typescript code lookup. Every time you look for a variable, you have to search the store file globally, hopping through various code fragments. Now this state management approach addresses this pain point: Some standard multi-language editors like VSCode and Atom can check static code, Ctrl+ click to jump to the corresponding position, mouse over a field, will get the type declaration and code snippets (js needs to be declared with jsDoc comments), at first I also thought this was useless. Javascript can also provide typescript type hints via jsDoc annotations.

Design patterns

Here, I recommend defining class as a module for better data management. The reason is that when the data is large, it can be divided into classes to facilitate state management. Why not use common object singleton? Because ordinary objects have no inheritance and no constructors that call themselves, it is difficult to define the type in TS, and there are no private, protected, and class classes that can be used as interfaces. In short, “not as flexible as class,” a habit I borrowed from game programming.

As I write this, I have already started using it in my actual project in 2017classalternativevuexSo there’s no need to worry about other problems, andVue 3.xThis is a programming design pattern, not a dialect of my own invention

Let’s start with the following code

const a = {
    data: {
        value: 10}}const b = {
    data: a.data
};

b.data.value = 20;

console.log(a, b); {/ / output data: {value: 20}}, {data: {value: 20}}
Copy the code

Here’s how it works: Because javascript variables are assigned to the same memory as Pointers, we rely on this feature to be directly equivalent to Vue assignments, so we can define some global state attributes and inject them into the components that need to be updated synchronously. Without further ado, let’s look at the code structure:

step 1 src/store/index.js

// Define a class for data management
class ModuleStore {

    /** Order information */
    orderInfo = {
        /** Order name */
        name: "Order" + Math.random().toString(36).substr(2),
        /** Order date */
        date: "2018/12/12 12:12:12"./** * Order status *@type {"ok"|"fail"|"invalid"|"wait"} Complete | | | invalid failure to pay * /
        state: "ok"}}/** Status management module */
const store = new ModuleStore;

export default store;
Copy the code

step 2 src/goods.vue

<script>
import store from ".. /store";
export default {
    data () {
        return {
            // The data used by the current component's responsitivity is the responsitivity of changing the value of store.orderInfo
            pageData: store.orderInfo
        }
    }
}
</script>
Copy the code

step 3 src/list.vue

<script>
import store from ".. /store";
export default {
    data () {
        return {
            // The data used by the current component's responsitivity is the responsitivity of changing the value of store.orderInfo
            listData: store.orderInfo
        }
    },
    methods: {
        modifyState() {
            // Changes are made here, and other references to Store.orderInfo will be synchronized to all components
            this.listData.state = "wait";
            / / or
            store.orderInfo.state = "wait"; }}}</script>
Copy the code

Matters needing attention

<script>
import store from ".. /store";
export default {
    ...more,
    methods: {
        modifyState() {
            // Be careful not to modify the entire property in this way, as this will cause all the properties in the object to lose Pointers (see the original code snippet).
            this.listData = {
            	name: "Modify order name".date: new Date().toLocaleString()
            }
            // But I don't want to
            // this.listdata. name = "change order name ";
            // this.listData.date = new Date().toLocaleString()
            // What to do?}}}</script>
Copy the code

Further state module design

  1. To create aModifyObject.js, can be divided into multiple ways to use:By default, it is exported to other modules for inherited use,Used as a normal singleton tool; intypescriptFor these two methods, I will use generics to constrain the passing parameters, thus ensuring the reliability of the status field.
// Since there may be more than one state module, define a base class and export it for other modules to inherit
// export is used by other modules
export class ModuleModifyObject {

    /** * Modifies the attribute value - only modifies the existing value *@param {object} Target Modified target *@param {object} Value Modified content */
    modifyData(target, value) {
        for (const key in value) {
            if (Object.prototype.hasOwnProperty.call(target, key)) { target[key] = value[key]; }}}/** * Sets property values - values that did not exist before are also set * based on the value passed in@param {object} Target Specifies the target *@param {object} Value Specifies the value */
    setData(target, value) {
        for (const key inobject) { target[key] = value[key]; }}}const modifyObject = new ModuleModifyObject();

// Export default is used as a singleton
export default modifyObject;
Copy the code
  1. Let’s do it againsrc/store/index.js
import {
    ModuleModifyObject
} from "./utils/ModifyObject"

class ModuleStore extends ModuleModifyObject {... more } ... moreCopy the code
  1. Now back tosrc/list.vueIn the
<script>
import store from ".. /store";
export default {
    ...more,
    methods: {
        modifyState() {
            const obj = {
                name: "Modify order name".date: new Date().toLocaleString(),
                // The following attributes are not stored in store.orderInfo. I have already done some processing in the base class above, so properties that do not already exist will not be changed
                a: "php".b: "java"
            }
            
            / / write one
            store.modifyData(this.listData, obj); 
            / / write two
            store.modifyData(store.orderInfo, obj);
       	    // This. Listdata. name = "change order name "; This.listdata.date = new date ().tolocaleString ()}}}</script>
Copy the code

In the real sense, it cannot be directly modifiedstore.xxxBut throughmutationsTo modify the state

1. Start switchingtypescriptFirst, we’ll define two core type tools that we’ll use later

/** Deep recursion all attributes are optional */
export type DeepPartial<T> = {
    [P inkeyof T]? : T[P]extends object ? DeepPartial<T[P]> : T[P];
}

/** Deep recursion all attributes are read-only */
export type DeepReadonly<T> = {
    readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
}
Copy the code

Give it a makeoverstoreModule; Note that this is not intended to be usedvuexthemutations, but through the corresponding method to constrain the use

interface UrderInfoType {
    /** User name */
    name: string
    /** Date of birth */
    date: string
    / * * sex * /
    gender: "man" | "woman"| ""
    /** Login information */
    api: {
        /** Login credentials */
        token: string
        /** Login 'id' */
        id: string}}class ModuleStore extends ModuleModifyObject {

    /** User information */
    readonly userInfo: DeepReadonly<UrderInfoType> = {
        name: "".date: "".gender: "".api: {
            token: "".id: "",}}/** * Update user information and cache it in 'sessionStorage' *@param Value Specifies the value to be updated */
    updateUserInfo(value: DeepPartial<UserInfoType>) {
        // the 'modifyData' method does recursive processing in it, you can see the following code repository is written, not explained here
        this.modifyData(this.userInfo, value);
        // or
        // this.modifyData(this.userInfo as DeepPartial<UserInfoType>, value);
        sessionStorage.setItem("user-info".JSON.stringify(this.userInfo)); }}Copy the code

3. Return to the usage scenario

<script>
import store from ".. /store";
export default {
    data () {
        return {
            // Mount reactive state
            userInfo: store.userInfo
        }
    },
    methods: {
    	setToken() {
            The "token" cannot be assigned because it is read-only.
            // this.userInfo.api.token = ""
            
            // Now only 'updateUserInfo' can be used to change the property to be changed, and there are type constraints and code prompts, no longer worry about state errors,
            // I'm not going to remember every attribute, so it's up to the machine to remember and prompt
            store.updateUserInfo({
            	api: {
                    token: "adfsdf2sd4f5s4d"}})}}}</script>
Copy the code

4. ContrastvuexIn themutations

const store = new Vuex.Store({
    state: {
        userInfo: {
            ...more 
        }
    },
    mutations: {
    	updateUserInfo(state, value) {
            state.userInfo = value;
            sessionStorage.setItem("user-info".JSON.stringify(state.userInfo)); }}})// If you do not want to change an attribute by overwriting the value of an old attribute, write this as an example

this.$store.commit(updateUserInfo, { ... this.$store.state.userInfo, ... {api: { 
        	token: "asfdf45s4d54s5d4f5".id: vuexStore.state.userInfo.api.id
        }
    }
})

// It's really ugly, and when you have a lot of attributes (object attributes are very deep), it's not always a guarantee that you'll get it right, know what I'm saying?
// In other words, you can set the modified data outside and then upload it. So what does it look like

const value = JSON.parse(JSON.stringify(this.$store.state.userInfo)); // It must be a deep copy, otherwise it will change to the state directly
value.api.token = "asfdf45s4d54s5d4f5"
this.$store.commit(updateUserInfo, value);

// What is the difference
Copy the code

This is the basic implementation of state management. Let’s talk about the state monitoring process I used in my project: Using Object.defineProperty or New Proxy in the ModuleStore class for more complex operations, custom classes for state management are easier to understand and more extensible.

The code editor (take vscode as an example) is very friendly to static code tracking.VuexI gave it up because it was too hard to find a property with a lot of code), especially if you have a lot of datats, cooperatereadonly,private,enumSuch as the use of keywords, maintenance, reading is simply more comfortable. Cons: You can’t use the browser’s vuex plug-in, but with static code analysis detection, you don’t need to debug the plug-in.

Vue 3.x can also be reused

Add reactive() to the corresponding properties in the Store module.

import { reactive } from "vue";

class ModuleStore {

    /** Order information */
    orderInfo = reactive({
        /** Order name */
        name: "Order" + Math.random().toString(36).substr(2),
        /** Order date */
        date: "2018/12/12 12:12:12"./** * Order status *@type {"ok"|"fail"|"invalid"|"wait"} Complete | | | invalid failure to pay * /
        state: "ok"})}... The code is omittedCopy the code

Again, use the two files above as examples

  1. src/list.vue
<template>
    <div class="list card">
        <h1 class="title">{{ orderInfo.name }}</h1>
        <code class="code_box">{{ orderInfo }}</code>
        <button class="button button_blue" @click="changeName()">Set 'orderInfo.name' to "Order"</button>
    </div>
</template>
<script>
import store from ".. /store";

export default {
    setup() {
        // Just reference it
        const orderInfo = store.orderInfo;
        
        function changeName() {
            orderInfo.name = "Order" + Math.random().toString(36).substr(2);
        }

        return {
            changeName,
            orderInfo
        }
    }
}
</script>
<style>
.list{ width: 100%; margin-bottom: 20px; padding: 8px; }
.list .title { font-size: 22px; margin-bottom: 14px; }
.list .code_box { font-size: 14px; margin-bottom: 10px; display: block; }
</style>
Copy the code
  1. src/goods.vue
<template>
    <div class="goods card">
        <h1 class="title">{{ orderInfo.name }}</h1>
        <code class="code_box">{{ orderInfo }}</code>
        <button class="button button_green" @click="changeName()">Set 'orderInfo.name' to a random string</button>
        <button class="button button_red" @click="changeState()">Modify ` orderInfo. State `</button>
    </div>
</template>
<script>
import store from ".. /store";

export default {
    setup() {
        // Just reference it
        const orderInfo = store.orderInfo;

        function changeName() {
            orderInfo.name = Math.random().toString(36).substr(2);
        }

        function changeState() {
            orderInfo.state = state.orderInfo.state == "wait" ? "ok" : "wait";
        }

        return {
            changeName,
            changeState,
            orderInfo
        }
    }
}
</script>
<style>
.goods{ width: 100%; margin-bottom: 20px; padding: 8px; }
.goods .title { font-size: 22px; margin-bottom: 14px; }
.goods .code_box { font-size: 14px; margin-bottom: 10px; display: block; }
</style>
Copy the code

Vue 3.x provides a Reactive () API that allows reactive variables to be extracted and declared elsewhere, making the code simpler and more intuitive. The above design pattern implements the same function in both cases.

Finally, attached are the used projects: vue-admin-template, uni-app-template

Blog address: Hjs’ blog

If you have any questions, please feel free to ask