头图

Take Vuex as a guide, get a glimpse of the whole picture of state management

杨成功
中文

As we all know, Vuex is Vue's official state management solution.

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 the project. However, because of its 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 to allocate the internal state of the component and the Vuex state?
  • What are the potential problems with using Vuex?

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

Next, please join me, starting from the origin, taking Vuex as an example, to jointly unveil the mystery of state management.

Outline preview

The content introduced in this article includes the following aspects:

  • State and the birth of components
  • Need state management?
  • Single data source
  • 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, their shared two capabilities have completely critiqued Jquery. These two capabilities are:

  1. Data Driven View
  2. componentization

The data-driven view makes us bid farewell to the era when we can only rely on manipulating the DOM to update the page. We no longer need to find the DOM through layers of find and modify its attributes 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 data-driven concept appeared, some variables were also given special meanings.

First of all, ordinary variables, which are no different from the JQ era, are only used to store data. In addition, there is a type of variables, which have a responsive role. 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, means 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.

State variables, now everyone seems to prefer to call them state . We often talk about state and state management. In fact, this state refers to state variables. The states mentioned below also refer to state variables.

a state, the component also comes.

In the JQ era, a front-end page is an html, without the concept of "components". For the public parts of the page, it is not too difficult to achieve elegant reuse. Fortunately, the three major frameworks have brought a very mature component design. It is easy to extract a DOM fragment as a component, and the component can maintain its own state, which is 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) passed by the parent component to display the status (Props), 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 the status of the current settings that will affect other components. Of course, it is unrealistic to isolate the component state completely. There will inevitably be a need for multiple components to share 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 shared state scheme 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 weak. for example:

  • The component level is too deep, and the state needs to be shared. At this time, the state has to be transferred layer by layer.
  • When a child component updates a state, there may be multiple parent components, which are shared by sibling components, which is difficult to implement.

In this case, continue to use the " extract state to the parent component " method, you will find it very complicated. And as the number of components increases and the nesting level deepens, the complexity becomes higher and higher. Because there are many associated states and complex transmission, it is easy to have problems such as inexplicable updates of a certain component, or failure to update a certain component, and it will be difficult to troubleshoot abnormalities.

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

Need state management?

As we mentioned in the previous section, with the complexity of the page, we have encountered thorny problems in the realization 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 have a common name, which is that 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 internal state of the component. It is maintained separately from the component, and then is associated with the component that needs this state in some way.

Each state management has its own implementation plan. Vue has Vuex, React has Redux, Mobx, and of course there are other solutions. But they all solve the same problem, which is the problem of cross-component state sharing .

I remember that the concept of "state management" became an indispensable part of application development in the past two years. 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 blindly follow the trend. So many examples of abusing state management later appeared.

Seeing this, you should know that state management is not necessary. The reason why it appears and what problems it is to solve are basically explained above. If you still don’t understand, please pause and read it again from the beginning. Don't think that the background of the birth of a technical solution is not important. If you don't understand what problem it appears to solve, then you can't really play its role.

Redux author has a famous saying: If you don't know whether you need Redux (state management), then you don't need it .

Well, if you are using state management, or need to use state management to help you solve problems, then we continue to look down.

Vuex

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

So how does Vuex solve the problem of cross-component state sharing? Let's explore together.

Create store

As we mentioned above, for the general component sharing state, the official recommendation is to " extract the state to the nearest parent component ". Vuex is a higher step, extracting all states to the root component , so that any component can access it.

You may ask: Doesn't this expose the state to the whole world? Doesn't it completely eliminate the advantages of modularity?

actually not. The main purpose of Vuex is to allow all components to access these states, completely avoiding the situation where the state of subcomponents cannot be accessed. Vuex puts all state data on one object, following the principle of single data source . But this does not mean that the state is stacked. Vuex has implemented its own modular scheme on this single state tree.

Don't worry, let's take a step by step and take a look at how to use Vuex.

Vuex exists as a plug-in of Vue. First, install npm:

$ npm install --save vuex

After installation, we create a new src/store , and put all Vuex related code 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 plug-in, and then export the Vuex.Store

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

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

The example derived above is usually called store . A store contains the stored state ( state ) and the function to 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 necessary to mount . The purpose of this step of mounting is only to facilitate access to the store instance we exported this.$store If it is not mounted, the direct import and use are the same.

Single data source (state)

In the previous step, we Vuex.Store . At least everyone 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, and its 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 like this:

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

Why should this point be emphasized? Because many friends think that Vuex can only be operated this.$store When it comes to non-components, for example, if you want to set a certain Vuex state in the request function, you don't know what to do.

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

import { mapState } from 'vuex'

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

Status update method (mutation)

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

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

The following defines a increment , and update the state of count

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

increment in the .vue component:

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

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

Synchronization Update

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

Why is it necessary to update synchronously? Because in the development process, we often track changes in the state. A common method is to debug in the browser console. The use of asynchronous update status in the mutation will also cause the status to be updated normally, but it will cause the developer tools to sometimes fail to track the changes in the status, and it will be very difficult to debug.

Vuex's positioning for mutation is to change the state, just change the state, don't participate in anything else. The so-called dedicated personnel do special tasks, which also helps us avoid mixing the changed state with our own business logic, and at the same time standardizes the function.

So what if you really need to update asynchronously?

Asynchronous update

Asynchronous update status is a very common scenario. For example, if the data returned by an interface request needs to be stored, it is an asynchronous update.

Vuex provides action to update the status asynchronously. Unlike mutation, action does not directly update the state, but indirectly updates the state by triggering the mutation. Therefore, even using action does not violate the principle of is 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 above-mentioned asynchronous, as well as data processing, submitting different mutations according to conditions, and so on. Look at 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 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 this.$store.commit method, and no action is used at all.

Is action dispensable?

No. Actions are really needed in certain scenarios. This will be discussed in the next article.

State Modularity (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 scheme
, Can prevent the state from being piled up and becoming bloated.

Vuex allows us to divide the store into modules, each of which has 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 equal to the page component.

In order to distinguish, we call the divided module submodule , and the global exposed global module .

Let's look at the basic usage:

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

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

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

You have discovered that access to the state of the submodule must be accessed through this.$store.state.[module name]. The triggering of the mutation is the same as that of the global module. There is no difference.

The principle of action is the same as that of mutation, so I won't go into details.

Namespaces

As mentioned above, the sub-module triggering mutation and action are the same as the global module, so suppose that there is a mutation setName Triggered in the component, which mutation will be executed?

is tested, will be executed. The official statement is: in order for multiple modules to respond to the same mutation or action.

In fact, I have not encountered the actual application scenario for the official compatibility. On the contrary, the false trigger caused a lot of troubles due to the mutation of the same name. Maybe the official is also aware of this problem, and the index later also made a module processing solution for mutation and action.

This solution is the namespace.

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

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

After opening the namespace, triggering the mutation becomes:

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

It can be seen that the submission parameters '[mutation]' changed '[module name]/[mutation]'.

Modular slot

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

Modularity is necessary, but the solution of this module, always feels a bit awkward to use .

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

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

this.$store.user.state.uname

But the actual API is 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 from the perspective of user experience, this is 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 this feature has not yet found an application scenario. And when the namespace is not configured, the name must be unique, otherwise it will cause false triggering.

Secondly, after using the namespace, the mutation is triggered like this:

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

This obviously treats the parameters separately, why is it not like this:

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

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

Why Tucao

The slots mentioned above are not just for complaining. The main reason is that there is still room for optimization.

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

What I hope is that, for example, if b, c are used in component A, and other sub-modules are not allowed to operate, then the modules to be used can be imported first, for example, write:

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 by seeing this, you have a deeper knowledge and understanding of state management and Vuex.

However, in this article we only introduce the Vuex solution, other solutions for state management, and our complaints above. Can we find a better implementation method? These are all waiting for us to try.

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

Wonderful in the past

This column will long-term output articles on front-end engineering and architecture directions. The following has been published:

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 contact for authorization.

阅读 1.7k

前端砍柴人
分享工程与架构,前端边界探索等实践

专注前端工程与架构产出

2k 声望
2.2k 粉丝
0 条评论
你知道吗?

专注前端工程与架构产出

2k 声望
2.2k 粉丝
文章目录
宣传栏