vuex简单使用(Vuex从入门到精通)
概念:专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件 ,对 vue 应 用中多个组件的共享状态进行集中式的管理(读/写) ,也是一种组件间通信的方 式 ,且适用于任意组件间通信 。
Vuex 实现了一个单向数据流 ,在全局拥有一个 State 存放数据 ,当组件要更改 State 中的数据时 ,必须通过 Mutation 提交修改信息 , Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新 。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action ,但 Action 也是无法直接修改 State 的 ,还是需要通过Mutation 来修改State的数据 。最后,根据 State 的变化 ,渲染到视图上 。
Vuex 的状态存储是响应式的 。当 Vue 组件从 store 中读取状态的时候 ,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新 。 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation 。这样可以方便地跟踪每一个状态的变化 。原理如图所示:
vuex为何学程序页面多, 数据变量多
不同组件数据保持同步 数据的修改都是可追踪一个户外商店有两名员工 ,张三和李四
一天的早上 ,他们分别对帐篷的数量做了一次盘点,发现一共有三个帐篷
张三卖出去俩个 ,他以为库存里还有一个
李四卖出去一个 ,他以为库存里还有两个
而事实上是 ,库存现在已经为零
如果他们再接受客户的预订 ,就会出现库存不足的情况
张三和李四因为没有保持库存的数量的同步导致了尴尬 ,这个就是所谓的数据保持同步
店长需要知道, 谁卖出了多少个帐篷 ,这个行为我们称之为数据修改是可追踪的
图示:
Vuex中存什么多个组件共享状态 ,才存储在Vuex中
某个组件中的私有数据 ,依旧存储在data中
例如:
登陆的用户名需要在首页, 个人中心, 结算页面使用, 用户名存在Vuex中
文章详情数据, 只有在文章详情页查看, 在自身data中声明
小结 什么是Vuex Vuex是Vue官方推荐的集中式状态管理机制 为何学Vuex 数据同步, 集中管理 Vuex中存什么 多个组件共享的值 二 、Vuex学习内容 目标 知道Vuex要学习什么 核心概念官网地址: https://vuex.vuejs.org/zh/
安装(固定)
配置项(固定)
配置项 含义 注意 state 单一状态树 类似data mutations 数据管家(同步) 唯一修改state地方 actions 异步请求 要改state需要提交给mutations getters vuex计算属性 类似computed modules 模块拆分 图示关系单一定义store对象, 里面5个配置项, 在任意组件可以使用
小结 Vuex五个核心概念是? state / mutations / actions / getters / modules 三、Vuex例子准备 目标创建项目, 为学习准备
需求1: App.vue(作为根组件)
需求2: 子组件Add和子组件Sub, 嵌入在App.vue里
需求3: 三个组件共享库存数据(保持同步)
工程准备初始化新的工程 vuex-demo
vue create vuex-demo清空欢迎界面
并设置如下三个组件, 目录如下:
|-components |---AddItem.vue |---SubItem.vue |-App.vue App.vue复制标签和样式, 引入AddItem和SubItem2个子组件显示
<template> <div id="app"> <h1>根组件</h1> <span>库存总数:</span> <input type="text"> <div> <AddItem></AddItem> </div> <hr> <div> <SubItem></SubItem> </div> </div> </template> <script> import AddItem from @/components/AddItem import SubItem from @/components/SubItem export default { components: { AddItem, SubItem } } </script> <style> #app { width: 300px; margin: 20px auto; border:1px solid #ccc; padding:4px; } </style> AddItem.vue <template> <div> <h3>AddItem组件</h3> <p>已知库存数: 0</p> <button>库存+1</button> </div> </template> <script> export default { } </script> SubItem.vue <template> <div> <h3>SubItem组件</h3> <p>已知库存数: 0</p> <button>库存-1</button> </div> </template> <script> export default { } </script> 小结 App下套用了AddItem和SubItem, 要在3个组件共享一个数据 四 、vuex-store准备 目标 创建store仓库 注入到Vue项目中 store概念每个 Vuex 应用的核心 store(仓库), 包含5个核心概念
Vuex目录和路由模块router/index.js - 类似, 维护项目目录的整洁 ,新建src/store/index.js文件
当然, 这个步骤并不是必需的
使用步骤工程中 - 下载vuex
yarn add vuexstore/index.js - 创建定义导出store对象
// 目标: 创建store仓库对象 // 1. 下载vuex: 终端命令(yarn add vuex) // 2. 引入vuex import Vue from vue import Vuex from vuex // 3. 注册 Vue.use(Vuex) // 4. 实例化store对象 const store = new Vuex.Store({}) // 5. 导出store对象 export default storemain.js - 导入注入到Vue中
import Vue from vue import App from ./App.vue import store from @/store // 导入store对象 Vue.config.productionTip = false new Vue({ // 6. 注入到Vue实例中(确保组件this.$store使用) // this.$store = store store, render: h => h(App), }).$mount(#app)请再次回忆一下vue-router的用法 ,是不是很像?
小结vuex的核心是什么?
store对象(包含5个核心属性)如何创建store对象?
工程下载vuex模块 store/index.js 引入注册 生成store对象导出 main.js - 导入注入 五 、vuex-state数据源 目标 定义state 直接使用state 辅助函数mapStatestate是唯一的公共数据源 ,统一存储
定义state在store/index.js定义state
语法:
/* const store = new Vuex.Store({ state: { 变量名: 初始值 } }) */具体代码:
const store = new Vuex.Store({ state: { count: 100 // 库存 } }) 使用state2种方式方式1: 组件内 - 直接使用
语法:
this.$store.state.变量名方式2: 组件内 - 映射使用 (推荐)
语法:
// 1. 拿到mapState辅助函数 import { mapState } from vuex export default { computed: { // 2. 把state里变量映射到计算属性中 ...mapState([state里的变量名]) } } AddItem直接用 <template> <div> <h3>AddItem组件</h3> <p>已知库存数: {{ $store.state.count }}</p> <button>库存+1</button> </div> </template> App.vue直接用计算属性count, 和输入框的v-model双向绑定
<input type="text" v-model="count"> <script> export default { computed: { count: { set(){}, get(){ return this.$store.state.count } } } } </script> SubItem映射用 <template> <div> <h3>SubItem组件</h3> <p>已知库存数: {{ count }}</p> <button>库存-1</button> </div> </template> <script> // 需求1: 映射state到计算属性 // 1. 拿到辅助函数 mapState // 2. 在computed内, ...mapState([state变量名]) // 3. 当计算属性使用 import { mapState } from vuex // let r = mapState([count]) // 提取store里的state叫count的变量 // console.log(r); // 返回值: {count: 函数体(return state里count的值)} export default { computed: { // 映射count, 得到对象展开, 合并到计算属性中 ...mapState([count]) }, } </script>整个过程的示意图如下
注意state是响应式的, 只要state值变化, 页面上使用的地方会自动更新同步
小结state作用?
定义全局状态数据源
state如何定义?
在store内, state: {变量名: 初始值}
state的值如何用到具体vue组件内?
直接使用 this.$store.state.变量名 映射使用 ...mapState([state的变量名]) 六 、vuex-mutations定义-同步修改 目标 定义mutations 定义mutationsmutations类似数据管家, 操作state里的数据
在store/index.js定义mutations
语法:
/* const store = new Vuex.Store({ mutations: { 函数名 (state, 可选值) { // 同步修改state值代码 } } }) */具体代码
const store = new Vuex.Store({ state: { count: 100 // 库存 }, mutations: { addCount (state, value) { // 负责增加库存的管家 state.count += value }, subCount (state, value) { // 负责减少库存的管家 state.count -= value }, setCount (state, value) { // 负责直接修改库存的管家 state.count = value; } } }) 注意 mutations是唯一能修改state的地方, 确保调试工具可以追踪变化 mutations函数内, 只能写同步代码, 调试工具可追踪变化过程 因为调试工具要立刻产生一次记录, 所以必须是同步的 小结mutations里函数作用?
负责修改state里的数据mutations只能写什么样的代码?
同步流程的代码 七 、vuex-mutations使用 目标 使用mutations2种方式 mutations注意事项 使用mutations的2种方式方式1: 组件内 - 直接使用
语法:
this.$store.commit("mutations里的函数名", 具体值)方式2: 组件内 - 映射使用
语法:
// 1. 拿到mapMutations辅助函数 import { mapMutations } from vuex export default { methods: { // 2. 把mutations里方法映射到原地 ...mapMutations([mutations里的函数名]) } } AddItem直接用 点击事件绑定 提交mutations传入值 <button @click="addFn">库存+1</button> <script> export default { methods: { addFn(){ this.$store.commit(addCount, 1) } } } </script> App.vue直接用 触发计算属性的set方法 提交mutations传入值 <span>库存总数: </span> <input type="text" v-model="count"> <script> export default { computed: { count: { set(val){ this.$store.commit(setCount, val) // 把表单值提交给store下的mutations }, get(){ return this.$store.state.count } } } } </script> SubItem映射用 点击事件 映射mutations的方法 调用mutations方法传值 <button @click="subFn">库存-1</button> <script> // 需求2: 映射mutations到方法里 // 1. 拿到辅助函数 mapMutations // 2. 在methods内, ...mapMutations([mutations函数名]) // 3. 当普通方法使用 import { mapMutations } from vuex export default { methods: { ...mapMutations([subCount]), subFn(){ this.subCount(1) } } } </script> 注意mutations函数上, 只能接收一个参数值, 如果传对个, 请传一个对象
小结mutations有哪2种使用方式?
直接使用 this.$store.commit()
映射使用 mapMutations把方法映射到组件内直接调用
state, mutations, 视图组件, 3个关系是什么?
八 、vuex-actions定义-异步修改 目标 定义actions 定义actions在store/index.js定义actions
语法:
/* const store = new Vuex.Store({ actions: { 函数名 (store, 可选值) { // 异步代码, 把结果commit给mutations给state赋值 } } }) */具体代码:
const store = new Vuex.Store({ // ...省略state和mutations此处 actions: { asyncAddCount(store, num){ setTimeout(() => { // 1秒后, 异步提交给add的mutations store.commit(addCount, num) }, 1000) }, asyncSubCount(store, num) { setTimeout(() => { // 1秒后, 异步提交给sub的mutations store.commit(subCount, num) }, 1000) } } }) 小结actions和mutations区别?
mutations里同步修改state
actions里放入异步操作
actions是否能操作state?
不建议, 要commit给mutations(为调试工具可追踪)
actions和mutations里函数, 第一个形参分别是什么?
mutations的是state
actions的是store
九 、vuex-actions使用 目标 使用actions 使用actions的2种方式方式1: 组件内 - 直接使用
语法:
this.$store.dispatch(actions函数名, 具体值)方式2: 组件内 - 映射使用
语法:
// 1. 拿到mapActions辅助函数 import { mapActions } from vuex export default { methods: { // 2. 把actions里方法映射到原地 ...mapActions([actions里的函数名]) } } AddItem直接用 点击事件 dispatch触发action <button @click="asyncAddFn">延迟1秒, 库存+5</button> <script> export default { methods: { asyncAddFn(){ this.$store.dispatch(asyncAddCount, 5) } } } </script> SubItem映射用 点击事件 映射actions的方法 调用actions的方法传值 <button @click="asyncSubFn">延迟1秒, 库存-5</button> <script> // 需求3: 映射actions到方法里 // 1. 拿到辅助函数 mapActions // 2. 在methods内, ...mapActions([actions函数名]) // 3. 当普通方法使用 import { mapActions } from vuex export default { methods: { ...mapActions([asyncSubCount]), asyncSubFn(){ this.asyncSubCount(5) } } } </script> 小结actions使用方式?
方式1: this.$store.dispatch(actions方法名字, 值)
方式2: ...mapActions([actions里的方法名]) 映射到原地使用
视图组件, state, mutations, actions的关系是?
十 、vuex-重构购物车-准备Store 目标 在现有项目如何集成vuex store准备 复制预习资料<shopcar-模板>到自己今天文件夹下 下载vuex store/index.js创建导出store对象 main.js把store引入, 然后注入到Vue实例 小结现有项目如何集成vuex
下载vuex
创建store对象并注入到Vue实例中
十一 、vuex-重构购物车-配置项(上午结束) 目标 准备state和mutations还有actions 配置项准备 定义state - 保存商品列表数组 state: { goodsList: [] // 列表 } 定义mutations - 给state里变量赋值 mutations: { setGoodsList(state, newList) { state.goodsList = newList } } 定义actions - 异步请求数据提交给mutations actions: { async asyncGetGoodsList(store) { const url = `https://www.escook.cn/api/cart` // 发送异步请求 const res = await axios({ url: url }); store.commit(setGoodsList, res.data.list) // 提交mutation修改state中的数据 } } App.vue使用vuex 把vuex商品数组映射回来使用 网络请求调用actions方法 <script> import { mapState, mapActions } from vuex export default { computed: { ...mapState({list: goodsList}) // 自定义原地属性名list, 映射vuex里的goodsList变量值 }, created(){ this.asyncGetGoodsList() }, methods: { ...mapActions([asyncGetGoodsList]), allFn(bool){ this.list.forEach(obj => obj.goods_state = bool) } } } </script> 小结mapState可以改变映射到原地的计算属性名吗?
可以的, 格式...mapState({计算属性名, state里要映射的变量名})
十二 、vuex-getters定义-计算属性 目标 getters概念 定义getters getters概念vuex身上的全局状态-计算属性, 类似于computed
getters 依赖于 state中原始数据的变化,并返回计算后的新数据
定义getters在store/index.js定义getters
语法:
/* const store = new Vuex.Store({ getters: { 计算属性名 (state) { return 值给计算属性 } } }) */具体代码
const store = new Vuex.Store({ // ...省略其他 getters: { allCount(state) { return state.goodsList.reduce((sum, obj) => { if (obj.goods_state === true) { // 选中商品才累加数量 sum += obj.goods_count; } return sum; }, 0) }, allPrice(state) { return state.goodsList.reduce((sum, obj) => { if (obj.goods_state) { sum += obj.goods_count * obj.goods_price } return sum; }, 0) } } }) 小结getters有什么用?
vuex里的计算属性, 属于全局计算属性, 类似computed
十三、vuex-getters使用 目标 组件内使用getters 使用getters的2种方式方式1: 组件内 - 直接使用
语法:
this.$store.getters.计算属性名方式2: 组件内 - 映射使用
语法:
// 1. 拿到mapGetters辅助函数 import { mapGetters } from vuex export default { computed: { // 2. 把getters里属性映射到原地 ...mapGetters([getters里的计算属性名]) } } MyFooter.vue里使用 使用2种方式给计算属性值 <script> import { mapGetters } from vuex export default { computed: { allCount(){ return this.$store.getters.allCount; }, ...mapGetters([allPrice]) } } </script> 小结getters如何使用?
方式1: this.$store.getters.计算属性名
方式2: ...mapGetters([getters里计算属性名])
十四 、vuex-modules定义-分模块 目标 为何要分模块 modules定义 为何分模块 代码上的对比 创建modules模块对象 新建store/modules/user.js 新建store/modules/cart.js语法: 对象里包含5个核心概念, 只有state变成函数形式
user.js - 用户模块对象
// 用户模块对象 const userModule = { state(){ return { name: "", age: 0, sex: } }, mutations: {}, actions: {}, getters: {} } export default userModulecart.js - 购物车模块对象
// 购物车模块对象 import axios from axios const cartModule = { state() { return { goodsList: [] } }, mutations: { setGoodsList(state, newList) { state.goodsList = newList } }, actions: { async asyncGetGoodsList(store) { const url = `https://www.escook.cn/api/cart` // 发送异步请求 const res = await axios({ url: url }); store.commit(setGoodsList, res.data.list) // 提交mutation修改state中的数据 } }, getters: { allCount(state) { return state.goodsList.reduce((sum, obj) => { if (obj.goods_state === true) { // 选中商品才累加数量 sum += obj.goods_count; } return sum; }, 0) }, allPrice(state) { return state.goodsList.reduce((sum, obj) => { if (obj.goods_state) { sum += obj.goods_count * obj.goods_price } return sum; }, 0) } } } export default cartModule 定义modules语法:
modules: { 模块名: 模块对象 } 把2个模块对象, 引回到store里注册 import Vue from vue import Vuex from vuex import cartModule from ./modules/cart import userModule from ./modules/user Vue.use(Vuex) const store = new Vuex.Store({ modules: { user: userModule, cart: cartModule } }) export default store 小结为什么分模块?
集中式管理项目过大, 变量过多, 会导致state臃肿, 难以维护
如何分模块?
定义模块对象, state变成函数返回对象形式, 每个模块都有state/mutations/actions/getters/modules
根store如何注册?
modules里 { 模块名: 模块对象 }
十五 、分模块-影响state取值方式 目的 只要分模块, state取值方式改变, 其他暂时不变 state使用方式修改方式1: 组件内 - 直接使用
原语法:
this.$store.state.变量名分模块后语法:
this.$store.state.模块名.变量名方式2: 组件内 - 映射使用
原语法:
...mapState([state里变量名]) ...mapState({变量名: "state里变量名"}) // 给映射过来的state起别的名字分模块后语法:
...mapState({ 变量名: state => state.模块名.变量名 }) App.vue-修改 computed: { // ...mapState({list: goodsList}) // 本地属性名list, 映射vuex里的goodsList变量值 // 方式1: 直接用 // list(){ // 这个list就是组件内普通的计算属性名 // return this.$store.state.cart.goodsList // }. // 方式2: 映射方式改变 ...mapState({list: state => state.cart.goodsList}) }, 小结分模块对什么有影响?
对state的取值方式有影响, 对其他暂无影响
state如何取值?
在组件使用的时候, 要state.模块名.变量名
十六、分模块-命名空间 目标 防止多个模块之间, mutations/actions/getters的名字冲突 开启命名空间在模块对象内设置namespaced: true
const moduleShopCar = { namespaced: true, state () {}, mutations: {}, actions: {}, getters: {}, modules: {} } state使用方式修改直接使用无变化: this.$store.state.模块名.变量名
辅助函数需要遵守格式
...mapState("模块名", [state变量名]) mutations使用方式修改方式1: 组件内 - 直接使用
原语法:
this.$store.commit("mutations里的函数名", 具体值)开命名空间后语法:
this.$store.commit("模块名/mutations里的函数名", 具体值)方式2: 组件内 - 映射使用
原语法:
...mapMutations([mutations里方法名])开命名空间后语法:
...mapMutations("模块名", [mutations里方法名]) actions使用方式修改方式1: 组件内 - 直接使用
原语法:
this.$store.dispatch("actions里的函数名", 具体值)开命名空间后语法:
this.$store.dispatch("模块名/actions里的函数名", 具体值)方式2: 组件内 - 映射使用
原语法:
...mapActions([actions里方法名])开命名空间后语法:
...mapActions("模块名", [actions里方法名]) getters使用方式修改方式1: 组件内 - 直接使用
原语法:
this.$store.getters.计算属性名开命名空间后语法:
this.$store.getters[模块名/计算属性名]方式2: 组件内 - 映射使用
原语法:
...mapGetters([getters里计算属性名])开命名空间后语法:
...mapGetters("模块名", [getters里计算属性名]) 小结 state和mutations, 在根store和开启命名空间里的区别?整个vuex的体系是?
十七 、Vuex的构造 1.actions 由子组件this.$store.dispatch(actions名,传递的数据)触发 传递的数据只能是单个数据 ,多个数据用对象传递 actions: { action名({commit} ,数据){...}} store中actions内,commit用于触发mutations ,actions只做业务逻辑 ,不修改state 2.mutations 由actions中的commit(mutations名,传递的数据)触发 mutations:{mutations名(state,数据){...}} store中的mutations内 ,mutations可以修改state中的数据 3.state 存储数据:state:{userInfor:{}} 可以通过mutations存储或者修改 ,也是getters里面计算属性的依据 4.getters 计算state中的数据:getters:{getters名(state){...}} state用于getters计算属性的依据 十八 、Vuex中的模块(module)和命名空间(namespaced) 1.模块(module) 由于使用单一状态树 ,应用的所有状态会集中到一个比较大的对象 。当应用变得非常复杂时 ,store 对象就有可能变得相当臃肿。
为了解决以上问题 ,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的state、mutations 、actions 、getters 、甚至是嵌套子模块 。 2.命名空间(namespaced)默认情况下 ,模块内部的 actions 、mutations 和 getters 是注册在全局命名空间的——这样使得多个模块能够对同一 mutations 或 actions 作出响应 ,如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后 ,它的所有 getters 、actions 及 mutations 都会自动根据模块注册的路径调整命名 。
如果你想模块之间相互独立 、互不影响 。可以通过添加 namespaced: true 的方式使其成为带命名空间的模块 ,当模块被注册后,它的所有 getters 、actions 和 mutations 都会自动根据模块注册的路径调整命名。所以开启命名空间的模块中的getters 、actions 和 mutations的使用方式都会改变;但是开启命名空间和不开启命名空间的模块中的state的使用方式不会改变 。格式依然是store.state.模块名.状态名 。
// 开启store子模块的命名空间 const moduleName = { state: {}, getters: {}, mutations: {}, actions: {}, namespaced: true // 开启命名空间 } export default moduleName // 在组件中使用 import { mapState, mapGetters, mapActions } from vuex export default { computed: { // 传统方式:获取store中的数据 proData () { return this.$store.state.productModule.proData }, themeData () { return this.$store.getters[themeModule/themeData] }, proName () { return this.$store.getters[productModule/proName] }, proDesc () { return this.$store.getters[productModule/proDesc] }, indexData () { return this.$store.getters[productModule/indexData] } // 辅助函数方式一:获取store中的数据(代码较简洁) ...mapState({ proData: state => state.productModule.proData }) ...mapGetters([themeModule/themeData]) ...mapGetters([themeModule/proName, themeModule/proDesc, themeModule/indexData]) // 辅助函数方式二:获取store中的数据(代码最简洁) ...mapState(productModule, { proData: state => state.proData }) ...mapGetters(themeModule, [themeData]) ...mapGetters(productModule, [proName, proDesc, indexData]) }, created () { // 传统方式:获取异步数据 this.$store.dispath(themeModule/queryThemeAction) this.$store.dispath(productModule/queryProDataAction) this.$store.dispath(productModule/queryIndexDataAction) // 辅助函数方式:获取异步数据(需要在methods中使用mapActions定义方法) this.queryThemeAction() this.queryProDataAction() this.queryIndexDataAction() }, methods: { ...mapActions(themeModule, [queryThemeAction]) ...mapActions(productModule, [queryProDataAction, queryIndexDataAction]) } } 扩展: 使用Devtools调试vuex数据优秀的调试工具可以使我们写程序事半功倍 ,最后我们再学习一下如果使用dev-tools来调试vuex中的数据 ,这也是数据可预测特性里不可缺少的一环
目标 掌握dev-tools调试vuex 理解什么是数据状态是可追踪的 如何进行调试注意只有vue+vuex的项目才可以用
调试信息说明创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!