This time I thoroughly understand Vuex! (Xiaobai level tutorial)

Get into the habit of writing together! This is the 6th day of my participation in the "Nuggets Daily New Plan·April Update Challenge",Click to view event details

foreword

The theme of today's article is Vuex, so it is best to have a certain foundation before reading this article. If you have Vuen't learned Vueit, I suggest you go and read it first Vue.js. However, I believe that for the front-end partners, the Vueframework will be more or less exposed. Well, without further ado, let's get to the point:

Note: Vue-cliDifferent versions may generate different project directories and methods, so you don't need to worry about them.

1. What is it Vuex?

1.1 Official explanation

VuexIt is a state management mode  specially developed for  Vue.js applications . It uses a centralized store to manage the state of all components of the application, and uses corresponding rules to ensure that the state changes in a predictable manner.

1.2 Personal summary

After reading the above paragraph, I think that most people are confused. Official words are usually obscure and difficult to understand, because they need to ensure authority and correctness. I will give you a simple understanding through my own understanding:

The so-called is Vuexactually a Vue.jsdata warehouse for design, which is to put the common data of each component into a warehouse for unified management, which not only makes the data sharing between non-parent and child components simple and clear, but also makes the program more feasible. Maintenance (extracting data), and as long as the data in the warehouse changes, the places where the data is referenced in other components will also be automatically updated.

2. Why use it Vuex?

2.1 Complex value transfer between components

如果你之前用过Vue.js开发过项目,你一定会被各个组件之间的传值搞得晕头转向,特别是非父子组件之间传值时。利用Vuex我们可以将组件之间共享的数据抽取出来,单独存放在一个store(仓库)中去,这样各个组件需要数据的时候直接去仓库里面拿就好了,不用组件之间复杂的传值了,而且需要改变数据的时候,只需要将仓库里面的数据更改即可,各个组件里面引用的地方会自动更新。

2.2 Vue中的单项数据流

与单向数据流对应的就是双向数据流:双向数据流在Vue中也叫做‘双向绑定’,其实现主要是依靠MVVM框架,在Vue中主要由三个部分组成:ViewViewModelModel。其中View可以简单的理解为视图层,Model可以简单的理解为数据层,其中View与Model之间是不能直接通信的,必须得依靠ViewModel中间件来完成。通过ViewModel就可以实现数据双向绑定,也就是ViewModel之间的同步是自动的,Model数据改变了View视图上的数据也会跟着改变,而不必手动去更新。具体的原理在这里不是重点,就不细说了,感兴趣的小伙伴可以参考:

送上一张图:

Vue中的单向数据流:理解单项数据流,我们先来简单看一下官网给出的一张图:

我们先来简单理解一下图中三个层说明的什么:View(视图)、Actions(响应状态变化,可以简单理解为methods等等)、State(数据源,简单理解就是数据)。从图中的箭头我们可以看出,我们通过Actions改变State(数据),然后View(视图)更新,这一个过程是单向的,不会发生View(视图)改变了,然后State(数据)更新的情况,这使得我们的数据可控。我们再来看一下官网给出的实例代码,简单明了:

new Vue({
  // state
  data () {
    return {
      count: 0
    }
  },
  // view
  template: `
    <div>{{ count }}</div>
  `,
  // actions
  methods: {
    increment () {
      this.count++
    }
  }
})
复制代码

从上面代码中可以很清楚的反映出图片中的流程。在Vue中,数据从父组件传递给子组件,只能是单向绑定,子组件内部不能直接修改从父级传过来的数据,这样做的好处是所有状态变化都可以被记录、跟踪,状态变化通过手动调用通知,源头易追溯,没有“暗箱操作”(ViewModel)。但是,当我们遇到多个组件同时共享一个状态(数据)时,并且都需要改变状态(数据)时,单项数据的简洁性很容易被破坏,即使我们使用了父子组件的单向数据绑定,但这种模式在这种情况下也会变得非常脆弱,使代码不易维护。

总结出来就以下两个问题容易破坏单向数据流:

多个视图依赖于同一状态。
来自不同视图的行为需要变更同一状态。

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。

3.什么情况下该使用Vuex?

Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。 如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。\

4.构建项目和引入Vuex

前面为什么要花这么大力气去讲这么多基础知识呢?俗话说:‘万丈高楼拔地起’,只有地基打得牢,后面才能进行得更加顺利。说了这么多,现在终于步如正题了,了解了为什么要使用Vuex,现在我们就一起探讨一下如何使用它:

4.1 构建vue-cli项目

为了方便我们后续的讲解和开发,这里我们使用vue的脚手架工具vue-cli来构建我们的项目,简单列一下操作步骤:

  1. 开发工具:VSCode
  2. 开发环境:Node.js环境(npm包管理工具)
  3. 安装vue-cli
  4. 创建vue-cli项目

(1)安装vue-cli

执行命令:

npm install -g vue-cli
复制代码

安装成功:

检查是否安装成功,执行命令:

vue -V
复制代码

出现版本号则安装成功:

(2)构建vue-cli项目

执行命令:

vue create [项目名称] 或者 vue init webpack(老版本)
复制代码

如图按需勾选是否安装(router建议安装):

安装完成后我们的项目结构大致如下样子,使用vue-cli3.X版本构建的可能会有所不同:

最后检查项目是否能够正常启动:

执行命令:

npm run dev
复制代码

出现如图则构建成功:

4.2 安装Vuex并引入项目

(1)安装vuex

执行命令:

npm install vuex --save
复制代码

如图:

(2)引入vuex

1.src目录下新建一个store文件夹,并在store目录下新建index.js

2.index.js里面使用vuex,添加如下代码:

import Vue from 'vue'
import Vuex from 'vuex'

//使用vuex
Vue.use(Vuex)

//导出store
export default new Vuex.Store({})
复制代码

3. 在main.js中引入store,然后全局注入一下,这样就可以在任何一个组件里面使用它了:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
//引入store
import store from './store'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  //注册store
  store,
  components: { App },
  template: '<App/>'
})
复制代码

5.开始使用Vuex

5.1 简单看图理解

在正式开始之前,我们先简单看一张图,这一张图很清晰的描述了使用vuex的整个流程,在这里也不用搞得多明白,心里有个概念就好了,到了后面的代码部分自然就会理解了:

我们可以看到最左边是我们的组件,可以简单理解为我们的视图,虚线框则是我们的vuex,这里面有一个State,可以把它理解为数据。以前我们将数据放在组件里面,要修改数据直接在组件里面修改就好了,现在数据放在了vuex仓库里面,我们要修改数据就得走一定的流程:

  • 我们需要在我们组件里面调用Dispatch()方法提交Actions(还记得最开始我们如何说的Actions吗?)\

  • Actions再通过Commit()方法提交Mutations(简单理解为真正的修改数据的方法)\

  • 通过Mutations里面的方法改变state(数据)\

  • 响应(渲染)到组件里面。\

5.2 简单理解StateActionsMutations

看了上面的图可能有一些小伙伴不太理解图里面的StateActionsMutations,这里我们就详细理解一下:

  • state是什么?

官方的话可能有些晦涩,我们可以简单的理解为:存数据的地方,所有的数据都要存在state里面。这样理解起来就简单多了

  • Actions是什么?

Actions和Mutations比较类似,包含的都是一些方法,不同的是Actions不能直接更改数据,它的作用是提交Mutations,Mutations里面包含的才是具体操作数据的方法。

  • Mutations是什么?

vuex中,唯一能够修改数据的方法就是提交mutation,简单来说mutations里面存的就是一些操作数据的方法。

5.3 代码实例

1. 将项目自动生成的HelloWorld.vue删除,新建三个我们自己的组件,分别是:ChildA.vueChildB.vueParent.vue,并配置路由:

项目结构变为:

ChildA.vue

<template>
  <div class="child-a">
    <p>ChildA:{{count}}</p>
    <button>ChildA-Add</button>
  </div>
</template>

<script>
export default {
  name: 'ChildA',
  data () {
    return {
      //我们不再将数据放到组件里
      //count: 0
    }
  }
}
</script>
<style scoped>
</style>
复制代码

ChildB.vue

<template>
  <div class="child-b">
      <p>ChildB:{{count}}</p>
      <button>ChildB-Add</button>
  </div>
</template>

<script>
export default {
  name: 'ChildB',
  data () {
    return {
      //count: 0
    }
  }
}
</script>
<style scoped>

</style>
复制代码

Parent.vue

<template>
  <div class="parent">
      <child-a></child-a>
      <child-b></child-b>
  </div>
</template>

<script>
import ChildA from './ChildA'
import ChildB from './ChildB'
export default {
  name: 'Parent',
  data () {
    return {

    }
  },
  components: {
      ChildA,
      ChildB
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
复制代码

修改router下的index.js

import Vue from 'vue'
import Router from 'vue-router'
import Parent from '@/components/Parent'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Parent',
      component: Parent
    }
  ]
})
复制代码

这个时候我们启动项目,浏览器访问:

我们可以看到,浏览器上面并没有显示{{count}}的内容,因为我们并没有在组件里面定义count,考虑到ChildAChildB公用一个数据count,所以我么们将count提出来,利用vuex的仓库进行管理,所以我们需要在store目录下里面的index.js里面定义count,那么怎么定义呢,我们一起来看看:

修改src/store/index.js文件代码:

import Vue from 'vue'
import Vuex from 'vuex'

//使用vuex
Vue.use(Vuex)

//导出store
export default new Vuex.Store({
    //将数据定义在state里面,state是一个对象
    state: {
        count: 0
    }
})
复制代码

我们已经定义好了count,那么要怎么在各个组件里面使用这个数据呢?我们可以在组件里面这样调用:

CHildA.vue

export default {
  name: 'ChildA',
  data () {
    return {
      //count: 0
    }
  },
  //通过计算属性来获得count
  computed: {
    count: function(){
      //通过vue的this.$store来获得state
      return this.$store.state.count
    }
  }
}
</script>
复制代码

ChildB.vue

<script>
export default {
  name: 'ChildB',
  data () {
    return {
      //count: 0
    }
  },
  computed: {
      count(){
          return this.$store.state.count
      }
  }
}
</script>
复制代码

从上面的代码我们可以很清楚的看到是如何获取到state里面的count的,很多小伙伴可能就有疑惑了,为什么要使用计算属性呢,而不把count定义在data里面,然后通过方法获取呢?在这里我们就要回顾一下vuex里面state的特性了,state状态是响应式的,也就是说如果state里面的count发生了变化,组件里面的数据是自动更新的,所以就需要用到computed属性了。对computed属性不熟的小伙伴可以参考:

关于this.$store:我们最开始把vuex挂载到了根组件上去,所以我们在任何地方都可以调用$store,和$router一个道理,就像是调用store这个大仓库里面的属性一样。

此时我们看到浏览器页面上已经出现了count的值:

2.给两个button添加点击事件,以此来更改count的值:

ChildA.vue(ChildB修改代码相同,所以不在这里重复展示)

<template>
  <div class="child-a">
    <p>ChildA:{{count}}</p>
    <button @click="handleClick(10)">ChildA-Add</button>
  </div>
</template>

<script>
export default {
  name: 'ChildA',
  data () {
    return {
      //count: 0
    }
  },
  computed: {
    count: function(){
      return this.$store.state.count
    }
  },
  methods: {
    handleClick:function(num){
    //通过dispatch触发actions中的方法countAdd,actions提交mutations,num是携带的参数
      this.$store.dispatch('countAdd',num)
    }
  }
}
</script>
<style scoped>

</style>
复制代码

修改src/store/index.js代码:

import Vue from 'vue'
import Vuex from 'vuex'

//使用vuex
Vue.use(Vuex)

//导出store
export default new Vuex.Store({
    state: {
        count: 0
    },
    //组件通过dispatch方法触发actions里面的countAdd方法,然后actions提交mutations里面的countAdd方法。
    actions: {
    //接收组件传过来的参数num,Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
        countAdd(context,num){
            context.commit('countAdd',num)
        }
    },
    mutations: {
    //传入一个state对象,接收传过来的参数num
        countAdd(state,num){
            state.count+=num
        }
    }
})
复制代码

上面的代码还是比较清晰明了的,较为重要的部分我也添加了注释。在这里我们给button按钮添加了一个点击事件handleClick(),这个方法的作用就是增加count的值,增加多少以传入的参数决定,在方法里面我们通过disptch()方法触发actions,然后actions在提交mutations,最终实现state数据的更改。在actions里面我们接收了一个context对象,可以简单将这个对象理解为store对象(其实不同)。

最终效果:

我们可以看到,点击任意组件的按钮,页面上的其他组件的count值也会跟着改变,因为这些组件通过vuex实现了count的共享,并且count的值是自动更新的,有没有觉得很奇妙!

3.直接出发actions

在刚才的组件中,我们通过dispatch方法出发actions的,那能不能直接触发actions呢?答案是可以的,此时我们的handleClick()方法可以改写为:

methods: {
    handleClick:function(num){
    //直接调用commit触发actions
      this.$store.commit('countAdd',num)
    }
}
复制代码

既然我们直接触发了actions,那么src/store/index.js里面的actions就可以去掉了,因为我们直接commit了。

那么src/store/index.js里面的代码就可以简化为:

import Vue from 'vue'
import Vuex from 'vuex'

//使用vuex
Vue.use(Vuex)

//导出store
export default new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        countAdd(state,num){
            state.count+=num
        }
    }
})
复制代码

启动项目可以发现效果是一样的。

到这里可能就有小伙伴疑问了,为什么不能直接执行mutations里面的方法?这样多简单,答案是不能的,具体原因就不再这里深究了,有兴趣的小伙伴可以参考:Mutations详解

5.4 使用对象展开运算符简化代码

代码写到这里,可能有小伙伴发现:如果我们很多地方需要用到store里面的数据,那我们就要在很多地方写this.$store。这样看起来代码是十分冗余的。所以为了解决这个问题,我们引入了对象展开运算符mapState 、mapMutations来简化。

运用mapState 、mapMutations,我们组件里面的代码就可以这样写:

ChileA.vue(Child.vue里面修改的代码与之类似)

<template>
  <div class="child-a">
  //使用新的countA
    <p>ChildA:{{countA}}</p>
    <button @click="handleClick(10)">ChildA-Add</button>
  </div>
</template>

<script>
//要想使用展开运算符,就要先引入
import { mapState, mapMutations } from 'vuex'
export default {
  name: 'ChildA',
  data () {
    return {
    }
  },
  computed: {
  //通过mapState获得state里面的count,并赋值给countA
    ...mapState({
      countA: 'count'
    })
  },
  methods: {
    handleClick:function(num){
      //this.$store.commit('countAdd',num)
      this.countAdd(num)
    },
    //通过展开运算符提交mutations里面的方法countAdd
    ...mapMutations(['countAdd'])
  }
}
</script>
<style scoped>

</style>
复制代码

看到这里有没有觉得代码简洁了不少呢,没有烦人的this.$store,当state里面的数据更多的时候,更能体现出此种方法的优势。

展开运算符是ES6的语法,不太了解的小伙伴可以参考:

在这里我们只用到了mapStatemapMutations,其实还有其他对象展开符,比如mapActions等等,在这里就不深究了。

5.5 Getter与Module

文章写到这儿,vuex基础知识应该差不多了,虽然我们的代码很简单,但是我们需要理解其中的原理,理解其中每一个知识点,比如对象展开符等等,只有理解透彻了,vuex才算是上手了。

下面我们简单讲解一下vuex里面的Getter和Module模块思想,这里我们粗略的过一遍,深入探讨我们放在下一篇文章。

  • Getter

It can be simply understood as the computed property of the store, which is similar to computed. mapGetters The helper function just  maps the store in  getter to the local computed property, which means that we can use it in the component in a similar way to mapState.

You can refer to:

I will put the specific content in the next article

  • Module

Module is actually very simple, we just need to look at the following code on the official website to understand:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
复制代码

This code is very clear, it is a sub-module idea. We used a single store before, but once the project is very large, a store will be very complicated, so we use the sub-module idea. Check the official website for details:

6. Summary

After the whole article was written, I have a deeper understanding of vuex. I summarized a few points (not a summary), which can be regarded as a wake-up call for knowledge:

  • vuex: state sharing
  • Why use vuex
  • store: shared warehouse
  • state: data storage
  • dispatch、actions、mutations
  • Data modification process
  • object expander
  • getter: the computed property of the store
  • module: modular idea

If you know the above points in your mind, then vuex must be an entry, and you will have to rely on yourself to get to the bottom of it. The article is an introduction, and the real application has to rely on yourself. There should be many deficiencies in the article, and I welcome you to correct my mistakes.


goodbye! See you in the next article!

Finally, put a picture for everyone to think about after dinner:

Guess you like

Origin juejin.im/post/7087100496762109983