Home  >  Article  >  Web Front-end  >  Take Vuex as an example to uncover the mystery of state management

Take Vuex as an example to uncover the mystery of state management

藏色散人
藏色散人forward
2021-12-16 15:46:081773browse

Using Vuex as the guide , 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 official website introduction is also concise and clear. Thanks to this, it is very easy to quickly integrate Vuex into your project. However, because of its flexible usage, many students are somewhat confused in the design and use of Vuex.

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

  • What is state management?
  • Why should I use Vuex?
  • How to distribute component internal state and Vuex state?
  • 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 to unveil the mystery of state management starting from the origin, using Vuex as an example.

Outline preview

The content introduced in this article includes the following aspects:

  • The birth of state and components
  • Does state management need to be performed?
  • Single data source
  • Status update method
  • Asynchronous update?
  • State modularization
  • Modular slots
  • Next step

The birth of state and components

Since Three Since the birth of the big framework, the two capabilities they share have completely hit Jquery. These two capabilities are:

  1. Data-driven view
  2. Componentization

Data-driven Views enable us to bid farewell to the era when we could only rely on manipulating the DOM to update pages. We no longer need to find the DOM through layers of find and then modify its properties and content every time we update the page. We can do these things 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 an ordinary variable, which is no different from the JQ era and is only used to store data. In addition, there is a type of variables that have a responsive effect. These variables are bound to the view. When the variables change, the view bound to these variables will also trigger corresponding updates. I call these variables State variables.

The so-called data-driven view, strictly speaking, means that state variables drive the view. With the popularity of Vue and React, the focus of front-end developers has gradually shifted from operating DOM to operating 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 state mentioned below also refers to state variables.

After the state is established, the components also come.

In the JQ era, a front-end page is just an html, and there is no concept of "component". It is not too difficult to elegantly reuse the public parts of the page. Fortunately, the three major frameworks have brought about very mature component designs. It is easy to extract a DOM fragment as a component, and the component can maintain its own state internally, making it 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 and automatically respond according to the changes.

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

The above shared status scheme is usually no problem and is also an officially recommended best practice.

But if your page is complex, you will find that it is still insufficient. For example:

  • The component hierarchy is too deep and needs to share state. At this time, the state must be passed layer by layer.
  • When a child component updates a state, there may be multiple parent components that are shared by sibling components, making implementation difficult.

In this case, if you continue to use the "Extract state to 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 transfers, it is easy for problems such as a component to be updated inexplicably or a component not to be updated, and abnormal troubleshooting to be difficult.

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

Need state management?

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

So is there a solution? Of course there is, and thanks to the efforts of community leaders, there is more than one plan. But these solutions all have a common name, which is what 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 matched with the needs in some way. The components of this state are associated.

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 in the past two years, because of the popularity of the concept of "state management", it seemed 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 to use, and how to use state management, and just follow the trend blindly. As a result, there have been many examples of abuse of state management.

Seeing this, you should know that state management is not necessary. Why it appears and what problems it solves are 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 is designed to solve, then you won’t be able to truly 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.

Okay, if you are using state management, or need to use state management to help you solve problems, then let's continue reading.

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 it together.

Create store

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

Maybe you will ask: Doesn’t this expose the state to the global situation? Wouldn’t it completely eliminate the advantages of modularity?

actually not. The main purpose of Vuex doing this is to allow all components to access these states and completely avoid the situation where the state of sub-components cannot be accessed. Vuex puts all state data on an object, following the Single Data Source principle. 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 first look at how to use Vuex.

Vuex exists as a plug-in for Vue. First install it with npm:

$ npm install --save vuex

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

Create a new index.js and write the following code. The main function of this code is to load the Vuex plug-in using the Vue.use method, 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: {}
})

We usually call the instance exported above store. A store contains stored state (state) and functions that modify the state (mutation). 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: This step of mounting is not necessary. The purpose of this mounting step is just to facilitate accessing our exported store instance through this.$store in the .vue component. If it is not mounted, it is the same as direct import.

Single data source (state)

In the previous step, we used the constructor Vuex.Store to create a store instance. 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. Its value is an object used to store state. Vuex uses the Single State Tree principle to place all states on this object to facilitate subsequent state location and debugging.

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

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

Now we want 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 we emphasize this point? Because many friends think that Vuex can only be operated through 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 obtaining multiple states easier.

import { mapState } from 'vuex'

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

状态更新方式(mutation)

Vuex 中的状态与组件中的状态不同,不能直接用 state.app_version='xx' 这种方式修改。Vuex 规定修改状态的唯一方法是提交 mutation

Mutation 是一个函数,第一个参数为 state,它的作用就是更改 state 的状态。

下面定义一个名叫 increment 的 mutation,在函数内更新 count 这个状态:

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

然后在 .vue 组件中触发 increment

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

这样绑定了 count 的视图就会自动更新。

同步更新

虽然 mutation 是更新状态的唯一方式,但实际上它还有一个限制:必须是同步更新

为什么必须是同步更新?因为在开发过程中,我们常常会追踪状态的变化。常用的手段就是在浏览器控制台中调试。而在 mutation 中使用异步更新状态,虽然也会使状态正常更新,但是会导致开发者工具有时无法追踪到状态的变化,调试起来就会很困难。

再有 Vuex 给 mutation 的定位就是更改状态,只是更改状态,别的不要参与。所谓专人干专事儿,这样也帮助我们避免把更改状态和自己的业务逻辑混起来,同时也规范了函数功能。

那如果确实需要异步更新,该怎么办呢?

异步更新

异步更新状态是一个非常常见的场景,比如接口请求回来的数据要存储,那就是异步更新。

Vuex 提供了 action 用于异步更新状态。与 mutation 不同的是,action 不直接更新状态,而是通过触发 mutation 间接更新状态。因此即便使用 action 也不违背 “修改状态的唯一方法是提交 mutation” 的原则。

Action 允许在实际更新状态前做一些副作用的操作,比如上面说的异步,还有数据处理,按条件提交不同的 mutation 等等。看一个例子:

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')
        }
      })
    }
  }
})

在组件中触发 action:

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

这些就是 action 的使用方法。其实 action 最主要的作用就是请求接口,拿到需要的数据,然后触发 mutation 修改状态。

其实这一步在组件中也可以实现。我看过一些方案,常见的是在组件内写一个请求方法,当请求成功,直接通过 this.$store.commit 方法触发 mutation 来更新状态,完全用不到 action。

难道 action 可有可无吗?

也不是,在特定场景下确实需要 action 的,这个会在下一篇说。

状态模块化(module)

前面讲过,Vuex 是单一状态树,所有状态存放在一个对象上。同时 Vuex 有自己的模块化方案
,可以避免状态堆砌到一起,变的臃肿。

Vuex 允许我们将 store 分割成模块(module),每个模块拥有自己的 state、mutation、action。虽然状态注册在根组件,但是支持模块分割,相当于做到了与页面组件平级的“状态组件”。

为了区分,我们将被分割的模块称为子模块,暴露在全局的称为全局模块

我们来看基础用法:

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

上面定义了 user 模块,包含了一个 state 和一个 mutation。在组件中使用方法如下:

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

大家发现了,访问子模块的 state 要通过 this.$store.state.[模块名称] 这种方式去访问,触发 mutation 则与全局模块一样,没有区别。

action 与 mutation 原理一致,不细说。

命名空间

上面说到,子模块触发 mutation 和 action 与全局模块一致,那么假设全局模块和子模块中都有一个名为 setName 的 mutation。在组件中触发,哪个 mutation 会执行呢?

经过试验,都会执行。官方的说法是:为了多个模块能够对同一 mutation 或 action 作出响应。

其实官方做的这个兼容,我一直没遇到实际的应用场景,反而因为同名 mutation 导致误触发带来了不少的麻烦。可能官方也意识到了这个问题,索引后来也为 mutation 和 action 做了模块处理方案。

这个方案,就是命名空间。

命名空间也很简单,在子模块中加一个 namespaced: true 的配置即可开启,如:

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

开启命名空间后,触发 mutation 就变成了:

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

可见提交参数由 '[mutation]' 变成了 '[模块名称]/[mutation]'

模块化的槽点

上面我们介绍了 Vuex 的模块化方案,将单一状态树 store 分割成多个 module,各自负责本模块状态的存储和更新。

模块化是必要的,但是这个模块的方案,用起来总觉得有点别扭

比如,总体的设计是将 store 先分模块,模块下在包含 state,mutation,action。

那么按照正常理解,访问 user 模块下 state 应该是这样的:

this.$store.user.state.uname

但是实际 API 却是这样的:

this.$store.state.user.uname

这个 API 仿佛是在 state 中又各自分了模块。我没看过源码,但从使用体验上来说,这是别扭一。

除 state 外,mutation,action 默认注册在全局的设计,也很别扭

首先,官方说的多个模块对同一 mutation 或 action 作出响应,这个功能暂无找到应用场景。并且未配 namespace 时还要保证命名唯一,否则会导致误触发。

其次,用 namespace 后,触发 mutation 是这样的:

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

这个明显是将参数单独处理了,为什么不是这样:

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

总体感受就是 Vuex 模块化做的还不够彻底。

为什么吐槽

上面说的槽点,并不是为了吐槽而吐槽。主要是感觉还有优化空间。

比如 this.$store.commit 函数可以触发任何 mutation 来更改状态。如果一个组件复杂,需要操作多个子模块的状态,那么就很难快速的找出当前组件操作了哪些子模块,当然也不好做权限规定。

我希望的是,比如在 A 组件要用到 b, c 两个子模块的状态,不允许操作其他子模块,那么就可以先将要用到模块导入,比如这样写:

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

这样按照模块导入,查询和使用都比较清晰。

下一步

前面我们详细介绍了状态管理的背景以及 Vuex 的使用,分享了关于官方 API 的思考。相信看到这里,你已经对状态管理和 Vuex 有了更深刻的认识和理解。

然而本篇我们只介绍了 Vuex 这一个方案,状态管理的其他方案,以及上面我们的吐槽点,能不能找到更优的实现方法,这些都等着我们去尝试。

下一篇文章我们继续深挖状态管理,对比 Vuex 和 React,Fluter 在状态管理实现上的差异,然后在 Vue 上集成 Mobx,打造我们优雅的应用。

The above is the detailed content of Take Vuex as an example to uncover the mystery of state management. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete