本片理解基于vue2对应的Vuex文档,结合了官网文档以及众多前辈大佬所发布的帖子,由衷表示感谢。

vuex的超详细讲解和具体使用细节记录


随着我们进一步扩展约定,即组件不允许直接变更属于 store 实例的 state,而应执行 action 来分发 (dispatch) 事件通知 store 去改变,最终达成了 Flux 架构。这样约定的好处是,能够记录所有 store 中发生的 state 变更,同时实现能做到记录变更、保存状态快照、历史回滚/时光旅行的先进的调试工具。

一、什么是Vuex?

Vuex是一个专门为Vue.js应用程序所设计开发出来的一个状态管理模式。它采用了集中式存储管理应用的所有组件状态,并且对于状态的变更建立了一套完整的使用规则,保证了状态变化的可预测性和安全性。

  1. 状态管理模式:类似于24种程序设计模式一样,在我们解决某一类特殊问题上提供了一种较为合理的参考方案和编程实现方案。
  2. 库:对于库的理解就是两个词集成封装,在解决这里问题的时候,不用你用最原始的数据结构和算法来一一实现每一个功能,站在巨人的肩膀上,直接调用库中集成好的API就可以了。
  3. 状态:状态即驱动应用的数据源(data)。我们知道灯泡开和关分别是一种固定状态,我们可用1代表开,0代表关。这样的话,用数字就可以代表状态了,那反过来说,状态就是数据的具体表现形式,所以我们可以这样理解:状态管理就是数据管理,进一步而言,vuex就是去管理vue中的数据的

二、vuex的使用场景有哪些?解决了什么样的问题?

这是一个简单的单项数据流示意图,在单个组件中数据-视图-操作构成一个回环。但是实际项目中可能需要我们打通组件之间(多个.vue页面)数据不互通的特点,类似于组件之间的通信,除了props,emit、refs、v-model、provide/inject、eventBus外就是vuex了,前面的几种方式在实现通信(数据共享)时,操作比较麻烦维护起来困难。由此,vuex将组件共享的状态(数据)单独抽取为一个全局的单例模式进行管理,这样无论项目中的哪一个组件都可以通过我们制定的规则,全局访问、操作我们的全局单例状态。

需要特别注意的是Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。相较于其他Flux实现,vuex是一个专门为vue.js设计开发的,融合了vue.js细粒度数据响应机制来进行高效的状态更新。
  2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

开发大型单页应用可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。vuex就像一个仓库,用来存放组件中需要用到的数据,至于管理,就是增删改查,往vuex中存取、修改、删除等操作

由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的 methods 中提交 mutation。

vuex的应用场景

  • 正常数据放到data里面就行了,免得麻烦,一般小项目都很少用到vuex,毕竟vuex的步骤稍微多一点
  • 一般公共数据放到vuex中会比较好,毕竟组件有自己的data可以存放数据,公共的变量如用户信息、token等。vuex存一份,localstorage再存一份,取数据直接从vuex里面取,取不到再从localstorage里面去取。
  • 跨很多组件层级的数据通信,也可以通过vuex去做管理,毕竟vuex就像一个对象一个,好多组件都指向这个对象,当这个vuex对象发生了变化,所有的组件中对应的内容都会发生变化,这样就能做到实时响应,一个变,大家都变。

三、vuex的基本组成和对应作用

  1. state

    Vuex使用单一状态树,每个应用将仅仅包含一个 store 实例,即一个对象就包含了整个应用程序所有使用到的状态,单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

    我们在组件中读取store中的状态一般在computed:{}计算属性中进行,这样每当状态发生变化的时候,都会自动求取计算属性,更新相关DOM。我们可以将同样的函数定义为一个方法,而不是一个计算属性。从最终结果来说,这两种实现方式确实是完全相同的。然而,不同的是计算属性将基于它们的响应依赖关系缓存。计算属性只会在相关响应式依赖发生改变时重新求值。这就意味着只要 author.books 还没有发生改变,多次访问 publishedBookMessage 时计算属性会立即返回之前的计算结果,而不必再次执行函数。

    注意:在每一个组件想要访问状态管理全局唯一实例时都需要import store from ‘store’,如果很多组件都有使用到,每个页面都插入一下,属实拉跨。Vuex 通过 Vue 的插件系统将 store 实例从根组件中“注入”到所有的子组件里。且子组件能通过 this.$store 访问到。(全局main.js配置)

    当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:

     computed: mapState({// 箭头函数可使代码更简练count: state => state.count,// 传字符串参数 'count' 等同于 `state => state.count`countAlias: 'count',})
    //当计算属性的名称和state中某个子节点相同,可以直接给mapState传对应字符串就好了。
    
  2. getter

    这个方法可以理解为我们需要获取某一状态集合中的部分值(子集),派生出一些状态,如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。由此推出了getter,将操作集成到store中

    Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值。getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。

    Getter 接受 state 作为其第一个参数

    const store = createStore({state: {todos: [{ id: 1, text: '...', done: true },{ id: 2, text: '...', done: false }]},getters: {doneTodos: (state) => {return state.todos.filter(todo => todo.done)}}
    })
    

    Getter 也可以接受其他 getter 作为第二个参数:

    getters: {// ...doneTodosCount (state, getters) {return getters.doneTodos.length}
    }
    

    通过方法访问,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。

    可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

    getters: {// ...getTodoById: (state) => (id) => {return state.todos.find(todo => todo.id === id)}
    }
    
    store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
    

    mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

    import { mapGetters } from 'vuex'export default {// ...computed: {// 使用对象展开运算符将 getter 混入 computed 对象中...mapGetters(['doneTodosCount','anotherGetter',// ...])}
    ///// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`doneCount: 'doneTodosCount'
    
  3. Mutation

    更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)**和一个**回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

    const store = createStore({state: {count: 1},mutations: {increment (state) {// 变更状态state.count++}}
    })
    

    你不能直接调用一个 mutation 处理函数。这个选项更像是事件注册:“当触发一个类型为 increment的 mutation 时,调用此函数。”要唤醒一个 mutation 处理函数,你需要以相应的 type 调用 store.commit 方法:注意使用commit来获取使用权限就可以了。

    除此之外外,mutation中函数还支持第二个参数,在这里叫做载荷,载荷的类型在多数情况下是一个对象,这样可以包含多个属性记录,在组件中使用方式形如:

    store.commit('increment', {amount: 10
    })
    

    一条重要的原则就是要记住 mutation 必须是同步函数,混合异步调用会导致你的程序很难调试.

    使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store

    import { mapMutations } from 'vuex'export default {// ...methods: {...mapMutations(['increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`// `mapMutations` 也支持载荷:'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`]),...mapMutations({add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`})}
    }
    
  4. Action

    Action 类似于 mutation,不同在于:

    • Action 提交的是 mutation,而不是直接变更状态。
    • Action 可以包含任意异步操作。

    还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:

    const store = createStore({state: {count: 0},mutations: {increment (state) {state.count++}},actions: {increment (context) {context.commit('increment')}}
    })
    

    Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters。

    实践中,我们会经常用到 ES2015 的参数解构来简化代码(特别是我们需要调用 commit 很多次的时候):

    一开始的时候建议还是按照上面方法的写法,虽然重复了点,但是便于掌握要点。下面这个利用参数解构简化代码,从中结构出commit,来进行的操作。

    actions: {increment ({ commit }) {commit('increment')}
    }
    

    Action 通过 store.dispatch 方法触发,同时Actions 支持同样的载荷方式和对象方式进行分发:

    //异步操作
    actions: {incrementAsync ({ commit }) {setTimeout(() => {commit('increment')}, 1000)}
    }
    

    组件中使用Action分发时添加参数的两种方式:

    // 以载荷形式分发
    store.dispatch('incrementAsync', {amount: 10
    })// 以对象形式分发
    store.dispatch({type: 'incrementAsync',amount: 10
    })
    

    在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

    import { mapActions } from 'vuex'export default {// ...methods: {...mapActions(['increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`// `mapActions` 也支持载荷:'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`]),...mapActions({add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`})}
    }
    

    Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?,Promise 是抽象的异步处理对象,可以简单的理解为Promise 实例生成以后,可以用 then 方法和 catch 方法分别指定 resolved 状态和 rejected 状态的回调函数,即成功的回调和失败的回调。

    store.dispatch('actionA').then(() => {// ...
    })
    

    这是曾经在项目中使用过示例:比较不完整,但是有使用到辅助函数来进行结构

    <script>
    import { mapState, mapMutations } from 'vuex'
    export default {computed: { ...mapState({tags: (state) => state.table.tabsList,}),},methods: {...mapMutations({close: 'closeTag',}),changeMenu(item) {this.$router.push({ name: item.name })this.$store.commit('selectMenu', item)},handleClose(tag, index) {let length = this.tags.length - 1this.close(tag)if (tag.name !== this.$route.name) {return}if (index === length) {this.$router.push({name: this.tags[index - 1].name,})} else {this.$router.push({ name: this.tags[index].name })}},},
    }
    </script>
    
  5. Model

    ​ 关于模块,可以理解为多个子项目的状态管理,因为状态树是全局唯一的,所以以模块的方式进行划分开来。需要注意的是命名问题。

四、搭建一个完整的vuex基本使用Demo

牢记 Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:

  1. 应用层级的状态应该集中到单个 store 对象中。
  2. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
  3. 异步逻辑都应该封装到 action 里面。

首先是我们的目录管理层级:

​ 每一个模块我们都写一个单独的js文件,通过export default { state{},mutations{},getter{},action{}}导出对象,在主配置文件中index.js中插入模块。配置信息如下:

//vue2
import Vue from 'vue'
import Vuex from 'vuex'
import table from'./table'
Vue.use(Vuex)export default new Vuex.Store({modules: {table}
})
//vue3
import { createStore}from 'vuex'
import table from'./table'export default createStore({modules:{table}
})

无疑,上面的搭建配置信息是最简单,最容易使用的。当你的项目比较大的时候就可以考虑一下分割相关代码到模块中。实现如官方所推荐的层级结构:

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── App.vue
│   └── ...
└── store├── index.js          # 我们组装模块并导出 store 的地方├── actions.js        # 根级别的 action├── mutations.js      # 根级别的 mutation└── modules├── cart.js       # 购物车模块└── products.js   # 产品模块

vue2简单的状态管理实例

​ 推荐参考前辈 :该例子通过一个msg作为状态量全局存储在单例树上,在一个组件中通过this.$store.(action->mutation->state)进行读取和修改。前辈在博客里面的内容是很清晰了,每一步的介绍也很精简,我就不造轮子了。下面我会通过实现一个菜单的面包板,来对状态管理做一个介绍。

效果图

第一步:npm下载

npm install vuex --save

第二步:创建store文件夹,并在index.js下引用,和全局挂载。

在main.js中需要将刚刚创建的文件引入,并挂载到全局vue实例上。

第三步,建立一个以菜单为元素的状态模块 table.js,完成后再index.js中添加,并在添加到modules中。

export default {//导出结构说明 一个模块的状态管理应该是 包含state(必选)、mutations(必选)、getter(可选)、action(可选异步)等对象state: {//控制侧边栏收起,这个读者可以忽略isCollapse: false,//用于显示面包屑当前所在页面,这个读者可以忽略currentMenu: null,// tabList用来存放已经点击打开的页面菜单,即面包板tabsList: []},mutations: {//可以直接操作状态,方法的收个参数为当前状态对象,第二个可选参数为载荷,一般为对象isCollapseMenu(state) {state.isCollapse = !state.isCollapse},//这个函数是这样运行的,首先在菜单栏目里点击某一个菜单选型,跳转后,全局方法调用来执行下面函数//并传入点击变量item,在这个函数体里面有两个修改state的内容(全局变量) 包括应用于顶部面包屑的currentMenu的值//以及页面标签页【用一个对象数组来存储标签内容】,先判断是否已经存在,如果不存在着就push进去·,否则不做操作【三目运算】selectMenu(state, val) {//val.name==='home'?(state.currentMenu=null):state.currentMenu=valif (val.name == 'workerhome') {state.currentMenu = null;}else{state.currentMenu=val//新增tabslistlet result=state.tabsList.findIndex(item=>item.name==val.name)result===-1?state.tabsList.push(val):''}},//用作标签页的删除closeTag(state,val){let result=state.tabsList.findIndex(item=>item.name===val.name)state.tabsList.splice(result,1)},// 删除所有,用于在用户推出时清空状态deleteAll(state){state.tabsList=[];},},getter:{// getter中我们可以定义一个函数,这个函数我们用来修改state中的值的,函数接收一个参数state,//这个state参数就是当前的state对象,通过这个参数可以加工state中的数据,加工好return出去,以供组件中使用gettersChange(state){return state.tabsList;}},action:{//有需要异步操作的异步请求的时候需要在这个对象中添加方法}
}

第四步,既然我们对菜单的状态管理的定义和操作已经完成,下面就是添加和展示部分了。

      menu: [{// lable:菜单名字 name path: '/',name: 'home',label: '首页',icon: 's-home',url: 'Home/Home',},{path: '/user',name: 'user',label: '客户信息管理',icon: 'user',url: 'User/userManage',},{path: '/worker',name: 'worker',label: '员工信息管理',icon: 'user',url: 'Worker/workerManage',},{path: '/project',name: 'project',label: '项目信息管理',icon: 'video-play',url: 'Project/projectManage',},{path: '/client',name: 'client',label: '用户账号管理',icon: 'user',url: 'Client/clientManage',},{path: '/Workerhome',name: 'workerhome',label: '员工首页',icon: 'user',url: 'WorkerHome/workerhome',},{path: '/projectMerberManage',name: 'projectMerberManage',label: '项目成员管理',icon: 'user',url: 'Project/projectMerberManage',},{label: '模型主管项目任务管理',icon: 'user',children: [{path: '/modelMerberManage',name: 'modelMerberManage',label: '分配任务',icon: 'setting',url: 'Model/modelMerberManage',},{path: '/modelApproval',name: 'modelApproval',label: '审批任务',icon: 'setting',url: 'Model/modelApproval',},{path: '/modelHistory',name: 'modelHistory',label: '处理历史',icon: 'setting',url: 'Model/modelHistory',},],},{label: '其他主管项目任务管理',icon: 'user',children: [{path: '/otherMerberManage',name: 'otherMerberManage',label: '分配任务',icon: 'setting',url: 'Other/otherMerberManage',},{path: '/otherApproval',name: 'otherApproval',label: '审批任务',icon: 'setting',url: 'Other/otherApproval',},{path: '/otherHistory',name: 'otherHistory',label: '处理历史',icon: 'setting',url: 'Other/otherHistory',},],},],

上面是我们定义的侧边栏菜单选项,需要注意的的是我们后面的路由使用的是this.$router.push({name:item})的形式,所以菜单的name值一定要和路由中你所设定的name值相等。不然会出现无法找到对应路由的错误。

侧边栏的页面效果实现,怎么样都可以,唯独要注意的操作时每一个具有实际地址意义的菜单都要绑定下面这个函数。什么是实际地址意义呢?即有路由的地址。

    clickMenu(item) {this.$router.push({ name: item.name })this.$store.commit('selectMenu', item)},

第五步,将菜单状态管理的列表数据在一个组件中展示出来

<template><div class="tabs"><el-tagclass="mytag"v-for="(tag,index) in tags":key="tag.name"size="medium":closable="tag.name !== 'home'":effect="$route.name === tag.name ? 'dark' : 'plain'"@click="changeMenu(tag)"@close="handleClose(tag, index)">{{ tag.label+index }}</el-tag></div>
</template><script>
import { mapState, mapMutations } from 'vuex'
export default {computed: { ...mapState({tags: (state) => state.table.tabsList,}),},methods: {...mapMutations({close: 'closeTag',}),//点击跳转到对应标签的页面changeMenu(item) {this.$router.push({ name: item.name })},//点击关闭对应标签handleClose(tag, index) {let length = this.tags.length - 1this.close(tag)//关闭的不是当前页,则不需要跳转,还停留在当前页if (tag.name !== this.$route.name) {return}//关闭的当前页 需要跳转展示列表// 当前页是最后一页,则需要跳转到它前一个//当前是不是最后一页,是中间页 则需要跳转到它后面一个if (index === length) {this.$router.push({name: this.tags[index - 1].name,})} else {this.$router.push({ name: this.tags[index].name })}},},
}
</script>
<style lang="scss" >
.tabs {padding: 10px;background: whitesmoke;
}
.mytag {float: left;margin-left: 15px;cursor: pointer;}
</style>

五、补充辅助函数

mapState 、mapMutations 、mapActions 、mapGetters、mapActions。可以理解为包装的语法糖,使得我们在获取对应数据的时候能够一次获得多个(数组解构,简化代码),使用的方式也比较简单,参照上面使用格式就可以了。

vue2中vuex状态管理的理解(菜单面包板)相关推荐

  1. uniapp中vuex状态管理

    应用中,保持登录状态是常见需求,本文讲解使用uni-app框架时如何保持用户登录状态. 即:初次进入应用为未登录状态------->登录---------->关闭应用,再次打开------ ...

  2. vue2项目复习01-关闭elint检校,src文件别名,路由传参的对象写法,代理解决跨域问题,nprogress,vuex状态管理库,store的模块式开发,节流与防抖,编程式导航+事件委托路由跳转

    1.关闭elint语法校验 创建vue.config.js //关闭elint语法校验 {lintOnSave:false; } 2.src文件夹配置别名 jsconfig.json配置别名 @代表s ...

  3. 在vue项目中引用vuex状态管理工具

    在vue项目中引用vuex状态管理工具 一.vuex是什么? 二.使用步骤 1.引入库 2.在main.js文件引入配置 3.配置store/index.js文件 4.获取state数据 5.获取ge ...

  4. Vuex 状态管理的工作原理

    Vuex 状态管理的工作原理 为什么要使用 Vuex 当我们使用 Vue.js 来开发一个单页应用时,经常会遇到一些组件间共享的数据或状态,或是需要通过 props 深层传递的一些数据.在应用规模较小 ...

  5. [vuex]状态管理vuex

    vuex 状态管理vuex,之前一般都是通过一个全局js文件实现全局设置,在vue中通过vuex进行管理 简介 vuex是专为vue.js应用程序开发的状态管理模式.它采用集中存储管理应用的所有组件的 ...

  6. vuex状态管理,用最朴实的话讲解最难懂的技术,

    一.案例演示 引入vuex 1.利用npm包管理工具,进行安装 vuex.在控制命令行中输入下边的命令就可以了. npm n install vuex --save 需要注意的是这里一定要加上 –sa ...

  7. 【3D商城】使用Vuex状态管理完成购物车模块

    [3D商城]使用Vuex状态管理完成购物车模块 创建购物车的全局数据 添加产品到购物车 导航栏的购物车模块 结果 常见问题总结 创建购物车的全局数据 在store的index.js中 ,创建购物车变量 ...

  8. Vue项目 成员管理系统 Vuex状态管理(10)

    Vuex是一个专为Vue.js应用程序开发的状态管理模式.它采用集中式储存管理应用的所有组件的转台并以相应的规则保证装填以一中可预测的方式发生变化. Vuex可以将组件中的某些属性.值或者方法拿出来统 ...

  9. 【vuex状态管理案例mutations和actions区别】

    目录 vuex 状态管理 传统组件传值的缺点 案例 加减 效果 现在我们希望它是两个计数器的数同时加加减~ 先来看一下减的 父组件 子组件 加的是一样的逻辑哦 虽然这样可以实现我们想要的效果但是,还是 ...

最新文章

  1. 网页截图工具CutyCapt
  2. View工作原理(三)视图大小计算过程(measure过程)
  3. Web Api如何传递POST请求
  4. 分析如下java代码片段,Java内部测试笔试题
  5. char截取字符串_字符串的排列(滑动窗口)
  6. vue.js 安装
  7. 数据脱敏和加密_Apache ShardingSphere数据脱敏全解决方案详解
  8. linux root权限不够_Linux基础篇之用户管理
  9. 问:为什么python中有了全局解释器锁GIL,还要有互斥锁?
  10. 随想录(为什么循环队列具有先天的并行性)
  11. 在DataWorks中实现指定UDF只能被指定账户访问
  12. Maven pom.xml配置详解
  13. PHP读取表格都是精度,php 小数精度问题
  14. c语言给数组整体赋值,c语言给数组赋值有哪些形式
  15. 【洛谷P4234】最小差值生成树
  16. 排序算法——梳排序 Comb sort
  17. 图片懒加载和Vue路由懒加载
  18. 浏览器主页进来是hao123
  19. 起始2021-01-15
  20. Android开发已经到了要烧香求职的地步了?

热门文章

  1. python编程入门指南磁力下载-实战Python语言实现BT种子转化为磁力链接
  2. 直播的用户体验体系与质量监控方案
  3. 【个人网站搭建】服务器、域名准备
  4. 基于ssm的房屋出租网
  5. Hadoop集群性能测试
  6. 什么是ssl证书,ssl证书有哪几种类型?
  7. 虚拟机屏幕自适应问题
  8. MFC下载网页简单实现
  9. Android正方教务系统课程表+查成绩+查考试安排
  10. 【信号与系统】如何求系统的冲激响应和阶跃响应