33
头图
The source of this article is the public number: Programmer succeeded

As we all know, Vuex is the official state management solution of Vue.

The usage and API of Vuex are not difficult, and the introduction on the official website is also concise and clear. Thanks to this, it is very easy to quickly integrate Vuex into a project. However, because of the flexible usage, many students are a little confused in the design and use of Vuex.

In fact, before using it, we might as well pause for a while and think about a few questions:

  • What is state management?
  • Why should I use Vuex?
  • How are component internal state and Vuex state distributed?
  • What are the potential problems with using Vuex?

If you are ambiguous about these questions, then congratulations, this article may be what you need.

Please join me below, starting from the origin, taking Vuex as an example, and jointly demystifying state management.

Outline preview

The content presented in this article includes the following aspects:

  • State and the Birth of Components
  • Need state management?
  • single source of truth
  • Status update method
  • Asynchronous update?
  • State Modularity
  • Modular slot
  • Next step

State and the Birth of Components

Since the birth of the three major frameworks, the two capabilities they have in common completely crit jQuery. The two abilities are:

  1. Data Driven Views
  2. Componentized

Data-driven views make us say goodbye to the era of updating pages only by manipulating the DOM. We no longer need to find the DOM through layers of find and then modify its properties and content every time the page is updated. These things can be achieved by manipulating data.

Of course, in the eyes of our front-end, data can basically be understood as variables that store various data types. After the concept of data-driven appeared, some variables were also given special meanings.

The first is ordinary variables, which are no different from the JQ era, and are only used to store data. In addition to this, there is also a type of variable, which has a responsive function. These variables are bound to the view. When the variable changes, the view bound to these variables will also trigger the corresponding update. I call this type of variable State variable .

The so-called data-driven view, strictly speaking, is that state variables are driving the view. With the vigorous popularity of Vue and React, the focus of front-end developers has gradually shifted from manipulating DOM to manipulating data, and state variables have become the core.

The state variable, now people seem to prefer to call it state . We often talk about the state of the word, state management, in fact, this state refers to the state variable. The states mentioned below also refer to state variables.

the state, the component also comes.

In the JQ era, a front-end page is just an html, and there is no concept of "components". For the public parts in the page, it is not too difficult to achieve elegant reuse. Fortunately, the three major frameworks have brought a very mature component design, which can easily extract a DOM fragment as a component, and the component can maintain its own state and be more independent.

An important feature of components is that these internal states are isolated from the outside. The parent component cannot access the internal state of the child component, but the child component can access the state (Props) displayed by the parent component and automatically respond according to changes.

This feature can be understood as the state is modularized. The advantage of this is that there is no need to consider that the state of the current settings will affect other components. Of course, it is unrealistic to completely isolate the component state. There must be multiple components sharing the state. The solution in this case is to extract the state to the parent component closest to these components and pass it down through Props.

The above-mentioned scheme of sharing state is not a problem under normal circumstances, and it is also an officially recommended best practice.

But if your page is complicated, you will find that it is still not enough. for example:

  • The component hierarchy is too deep, and the state needs to be shared. At this time, the state needs to be passed layer by layer.
  • Child components update a state, there may be multiple parent components, and sibling components share it, which is difficult to implement.

In this case, continue to use the " extract state to the parent component " method and you will find it complicated. And as the number of components increases and the nesting level deepens, the complexity also increases. Because there are many associated states and complex transmission, it is easy to have problems such as inexplicable updates of a component, or a component that is not updated in life or death, and abnormal troubleshooting will also be difficult.

In view of this, we need a more elegant solution to deal with the state of this complex situation.

Need state management?

We mentioned in the previous section that with the complexity of the page, we encountered a difficult problem in the implementation of shared state across components.

So is there a solution? Of course there are, thanks to the efforts of the community leaders, there is more than one plan. But these programs all have a common name, which we discussed very intensely two years ago -- State Management .

State management can actually be understood as global state management , the state here is different from the state inside the component, it is maintained independently of the component, and then in some way associated with the component that needs this state.

Each state management has its own implementation scheme. Vue has Vuex, React has Redux, Mobx, and of course other solutions. But they all solve one problem, which is the problem of state components.

I remember that in the first two years, because of the popularity of the concept of "state management", it seems to have become an indispensable part of application development. Taking Vue as an example, creating a project will inevitably introduce Vuex for state management. But many people don't know why, when, and how to use state management. They just follow the trend blindly, so there are many examples of abusing state management.

Seeing this, you should know that state management is not necessary. Why it appears and what problem it is going to solve is basically explained above. If you don't get it, pause and read it again from the beginning. Don't think that the background in which a technical solution is born is unimportant. If you don't understand what problem it appears to solve, then you can't really play its role.

The Redux author has a famous quote: If you don't know if you need Redux (state management), you don't need it .

Well, if you are using state management, or need to use state management to help you solve problems, let's move on.

Vuex

Vue is widely used in China, especially in small and medium-sized teams. Therefore, the first state management solution that most people come into contact with should be Vuex.

So how does Vuex solve the problem of state sharing across components? Let's explore together.

create store

As we mentioned above, for the general component shared state, the official recommendation is " to extract the state to the nearest parent component ". Vuex takes it a step further and extracts all state to the root component so that any component can access it.

You may ask: doesn't this expose the state to the global? Wouldn't that completely eliminate the advantages of modularity?

actually not. The main purpose of Vuex doing this is to make these states accessible to all components, completely avoiding the situation where the state of child components cannot be accessed. puts all state data on one object, following the principle of a single source of . But this does not mean that the state is stacked, Vuex implements its own modular solution on this single state tree.

Don't worry, let's go step by step and see how to use Vuex first.

Vuex exists as a Vue plugin, first npm install:

$ npm install --save vuex

After installation, we create a new src/store folder and put all Vuex related codes here.

Create a new index.js and write the following code. The main function of this code is to use the Vue.use method to load the Vuex plugin, and then export the configured Vuex.Store instance.

import Vue from 'vue'
import Vuex from 'vuex'
// 安装插件
Vue.use(Vuex)

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: {}
})

The example exported above is usually called store . A store contains the stored state ( state ) and functions that modify the state ( mutation ), etc. All states and related operations are defined here.

The last step is to mount the store instance exported above to Vue in the entry file:

import store from './store'

new Vue({
  el: '#app',
  store: store
})

Note: is not required for this step to mount . The role of this step of mounting is just to facilitate access to the store instance we exported through this.$store in the .vue component. If it is not mounted, it is the same for direct import and use.

A single source of truth (state)

In the previous step, we used the constructor Vuex.Store to create a store instance, and everyone at least knows how to use Vuex. In this step, let's take a look at the specific configuration of the Vuex.Store constructor.

The first is the state configuration, whose value is an object used to store the state. Vuex uses the single state tree principle to place all states on this object, which is convenient for subsequent state positioning and debugging.

For example, we have an initial state app_version representing the version, as follows:

new Vuex.Store({
  state: {
    app_version: '0.1.1'
  }
}

Now to get it in the component, you can do this:

this.$store.state.app_version

But this is not the only way to get it, it can also be:

import store from '@/store' // @ 表示 src 目录
store.state.app_version

Why stress this point? Because many friends think that Vuex can only be operated through this.$store . When it comes to non-components, such as setting a certain Vuex state in the request function, I don't know what to do.

In fact, there are more elegant ways to get state in components, such as the mapState function, which makes it easier to get multiple states.

import { mapState } from 'vuex'

export default {
  computed: {
    ... // 其他计算属性
    ...mapState({
      version: state => state.app_version
    })
  }
}

Status update method (mutation)

The state in state.app_version='xx' is different from the state in the component and cannot be modified directly in this way. Vuex stipulates that the only way to modify state is to commit mutation .

Mutation is a function, the first parameter is state, its role is to change the state of the state.

The following defines a mutation named increment , and updates the state of count within the function:

new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment(state, count) {
      // 变更状态
      state.count += count
    }
  }
})

Then trigger increment in the .vue component:

this.$store.commit('increment', 2)

In this way, the view bound to count will be updated automatically.

Synchronization Update

While mutation is the only way to update state, it actually has a limitation: must be a synchronous update .

Why does it have to be a synchronous update? Because during development, we often track state changes. A common method is to debug in the browser console. Using asynchronous update state in mutation will also make the state update normally, but it will cause the developer tools to sometimes fail to track the state change, and it will be very difficult to debug.

In addition, Vuex's positioning for mutation is to change the state, just change the state, and don't participate in others. The so-called special people do special things, which also helps us avoid mixing state changes with our own business logic, and also standardizes functions.

So what if you really need asynchronous updates?

Asynchronous update

Asynchronous update of the state is a very common scenario. For example, if the data requested by the interface needs to be stored, that is asynchronous update.

Vuex provides action for updating state asynchronously. Unlike mutations, actions do not update the state directly, but indirectly update the state by triggering the mutation. Therefore, even using action does not violate the principle of " the only way to modify the state is to submit mutation ".

Action allows to do some side-effect operations before actually updating the state, such as the asynchronous mentioned above, as well as data processing, submitting different mutations according to conditions, etc. See an example:

new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    add(state) {
      state.count++
    },
    reduce(state) {
      state.count--
    }
  },
  actions: {
    increment(context, data) {
      axios.get('**').then(res => {
        if (data.iscan) {
          context.commit('add')
        } else {
          context.commit('reduce')
        }
      })
    }
  }
})

Trigger the action in the component:

this.$store.dispatch('increment', { iscan: true })

These are how actions are used. In fact, the main function of action is to request the interface, get the required data, and then trigger the mutation to modify the state.

In fact, this step can also be implemented in the component. I have seen some solutions. The common one is to write a request method in the component. When the request is successful, the mutation is triggered directly through the this.$store.commit method to update the state, and no action is used at all.

Is action dispensable?

No, action is really needed in certain scenarios, which will be discussed in the next article.

State modularization (module)

As mentioned earlier, Vuex is a single state tree, and all states are stored on one object. At the same time, Vuex has its own modular solution
, you can avoid the state stacking together and become bloated.

Vuex allows us to split the store into modules, each with its own state, mutation, and action. Although the state is registered in the root component, it supports module division, which is equivalent to a "state component" that is level with the page component.

To distinguish, we call the split module submodule , and the exposed global module global module .

Let's look at the basic usage:

new Vuex.Store({
  modules: {
    user: {
      state: {
        uname: 'ruims'
      },
      mutation: {
        setName(state, name) {
          state.name = name
        }
      }
    }
  }
})

The user module is defined above, which contains a state and a mutation. The method used in the component is as follows:

// 访问状态
this.$store.state.user.uname
// 更新状态
this.$store.commit('setName')

We have found that accessing the state of a submodule needs to be accessed through this.$store.state.[module name], and triggering mutation is the same as the global module, there is no difference.

Action is consistent with the principle of mutation, so I won't go into details.

Namespaces

As mentioned above, the submodule triggers the mutation and action to be consistent with the global module, so assume that there is a mutation named setName in both the global module and the submodule. Triggered in the component, which mutation will be executed?

tested and will execute . The official statement is: for multiple modules to be able to respond to the same mutation or action.

In fact, the official compatibility, I have never encountered actual application scenarios, but caused a lot of trouble due to the false trigger caused by the mutation of the same name. Maybe the official also realized this problem, and the index also made a module processing plan for mutation and action.

This scheme is the namespace.

The namespace is also very simple. Add a configuration of namespaced: true to the submodule to enable it, such as:

new Vuex.Store({
  modules: {
    user: {
      namespaced: true,
      state: {}
    }
  }
})

After enabling the namespace, the trigger mutation becomes:

this.$store.commit('user/setName')

It can be seen that the submission parameter has changed from '[mutation]' to '[module name]/[mutation]' .

Modular slot

Above, we introduced the modular solution of Vuex, which divides a single state tree store into multiple modules, each responsible for the storage and update of the state of this module.

Modularization is necessary, but the solution of this module, always feels a little awkward .

For example, the overall design is to divide the store into modules first, and the modules contain state, mutation, and action.

Then according to normal understanding, the state under the access user module should be like this:

this.$store.user.state.uname

But the actual API looks like this:

this.$store.state.user.uname

This API seems to be divided into modules in the state. I haven't read the source code, but in terms of experience, it's awkward.

In addition to state, mutation and action are registered in the global design by default, which is also very awkward .

First of all, the official said that multiple modules respond to the same mutation or action, and there is no application scenario for this function. And if the namespace is not configured, the name must be unique, otherwise it will cause false triggering.

Second, after using namespace, trigger mutation is like this:

this.$store.commit('user/setName')

This obviously handles the parameters separately, why not:

this.$store.user.commit('setName')

The overall feeling is that Vuex's modularization is not thorough enough.

why complain

The slot points mentioned above are not for the sake of complaining. The main reason is that there is still room for optimization.

For example this.$store.commit function can trigger any mutation to change the state. If a component is complex and needs to operate the status of multiple sub-modules, it is difficult to quickly find out which sub-modules are operated by the current component, and of course it is not easy to make permission regulations.

What I hope is that, for example, the state of two submodules of b, c is used in the A component, and other submodules are not allowed to operate, then the modules to be used can be imported first, for example:

import { a, b } from this.$store
export default {
  methods: {
    test() {
      alert(a.state.uname) // 访问状态
      a.commit('setName')// 修改状态
    }
  }
}

In this way, according to the module import, the query and use are relatively clear.

Next step

Earlier, we introduced the background of state management and the use of Vuex in detail, and shared our thoughts on the official API. I believe that after seeing this, you have a deeper understanding and understanding of state management and Vuex.

However, in this article, we only introduce the Vuex solution, other solutions for state management, as well as our complaints above, whether we can find a better implementation method, these are all waiting for us to try.

In the next article, we will continue to dig deep into state management, compare Vuex and React, and the differences in state management implementation in Fluter, and then integrate Mobx on Vue to create our elegant application.

Wonderful past

This column will output long-term articles on front-end engineering and architecture, which have been published as follows:

If you like my article, please like and support me! Also welcome to follow my column.

statement: This article is original, if you need to reprint, please add WeChat ruidoc to contact authorization.


杨成功
3.9k 声望12k 粉丝

分享小厂可落地的前端工程与架构