组件关系
数据通信
父子关系
父传子:props ; 子传父:$emit
非父子关系
vuex (一种组件通信方案)
概念:专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应 用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方 式,且适用于任意组件间通信。
Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 提交修改信息, Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action ,但 Action 也是无法直接修改 State 的,还是需要通过Mutation 来修改State的数据。最后,根据 State 的变化,渲染到视图上。
原理如图所示:
程序页面多, 数据变量多
一个户外商店有两名员工,张三和李四
一天的早上,他们分别对帐篷的数量做了一次盘点,发现一共有三个帐篷
张三卖出去俩个,他以为库存里还有一个
李四卖出去一个,他以为库存里还有两个
而事实上是,库存现在已经为零
如果他们再接受客户的预订,就会出现库存不足的情况
张三和李四因为没有保持库存的数量的同步导致了尴尬,这个就是所谓的
数据保持同步
店长需要知道, 谁卖出了多少个帐篷,这个行为我们称之为
数据修改是可追踪的
图示:
多个组件共享状态,才存储在Vuex中
某个组件中的私有数据,依旧存储在data中
例如:
登陆的用户名需要在首页, 个人中心, 结算页面使用, 用户名存在Vuex中
文章详情数据, 只有在文章详情页查看, 在自身data中声明
什么是Vuex
为何学Vuex
Vuex中存什么
安装(固定)
配置项(固定)
配置项
含义
注意
state
单一状态树
类似data
mutations
数据管家(同步)
唯一修改state地方
actions
异步请求
要改state需要提交给mutations
getters
vuex计算属性
类似computed
modules
模块拆分
单一定义store对象, 里面5个配置项, 在任意组件可以使用
Vuex五个核心概念是?
创建项目, 为学习准备
需求1: App.vue(作为根组件)
需求2: 子组件Add和子组件Sub, 嵌入在App.vue里
需求3: 三个组件共享库存数据(保持同步)
初始化新的工程 vuex-demo
vue create vuex-demo
清空欢迎界面
并设置如下三个组件, 目录如下:
|-components
|---AddItem.vue
|---SubItem.vue
|-App.vue
复制标签和样式, 引入AddItem和SubItem2个子组件显示
<template>
<div id="app">
<h1>根组件</h1>
<span>库存总数:</span>
<input type="text">
<div style="border:1px solid black; width: 300px;">
<AddItem></AddItem>
</div>
<hr>
<div style="border:1px solid black; width: 300px;">
<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>
<template>
<div>
<h3>AddItem组件</h3>
<p>已知库存数: 0</p>
<button>库存+1</button>
</div>
</template>
<script>
export default {
}
</script>
<template>
<div>
<h3>SubItem组件</h3>
<p>已知库存数: 0</p>
<button>库存-1</button>
</div>
</template>
<script>
export default {
}
</script>
每个 Vuex 应用的核心 store(仓库), 包含5个核心概念
和路由模块router/index.js - 类似, 维护项目目录的整洁,新建src/store/index.js文件
当然, 这个步骤并不是必需的
工程中 - 下载vuex
yarn add vuex
store/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 store
main.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对象?
state是唯一的公共数据源,统一存储
在store/index.js定义state
语法:
/*
const store = new Vuex.Store({
state: {
变量名: 初始值
}
})
*/
具体代码:
const store = new Vuex.Store({
state: {
count: 100 // 库存
}
})
方式1: 组件内 - 直接使用
语法:
this.$store.state.变量名
方式2: 组件内 - 映射使用 (推荐)
语法:
// 1. 拿到mapState辅助函数
import { mapState } from 'vuex'
export default {
computed: {
// 2. 把state里变量映射到计算属性中
...mapState(['state里的变量名'])
}
}
<template>
<div>
<h3>AddItem组件</h3>
<p>已知库存数: {{ $store.state.count }}</p>
<button>库存+1</button>
</div>
</template>
计算属性count, 和输入框的v-model双向绑定
<input type="text" v-model="count">
<script>
export default {
computed: {
count: {
set(){},
get(){
return this.$store.state.count
}
}
}
}
</script>
<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组件内?
mutations类似数据管家, 操作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里函数作用?
mutations只能写什么样的代码?
方式1: 组件内 - 直接使用
语法:
this.$store.commit("mutations里的函数名", 具体值)
方式2: 组件内 - 映射使用
语法:
// 1. 拿到mapMutations辅助函数
import { mapMutations } from 'vuex'
export default {
methods: {
// 2. 把mutations里方法映射到原地
...mapMutations(['mutations里的函数名'])
}
}
点击事件绑定
提交mutations传入值
触发计算属性的set方法
提交mutations传入值
库存总数:
点击事件
映射mutations的方法
调用mutations方法传值
mutations函数上, 只能接收一个参数值, 如果传对个, 请传一个对象
mutations有哪2种使用方式?
直接使用 this.$store.commit()
映射使用 mapMutations把方法映射到组件内直接调用
state, mutations, 视图组件, 3个关系是什么?
在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
方式1: 组件内 - 直接使用
语法:
this.$store.dispatch('actions函数名', 具体值)
方式2: 组件内 - 映射使用
语法:
// 1. 拿到mapActions辅助函数
import { mapActions } from 'vuex'
export default {
methods: {
// 2. 把actions里方法映射到原地
...mapActions(['actions里的函数名'])
}
}
点击事件
dispatch触发action
点击事件
映射actions的方法
调用actions的方法传值
actions使用方式?
方式1: this.$store.dispatch('actions方法名字', 值)
方式2: …mapActions(['actions里的方法名']) 映射到原地使用
视图组件, state, mutations, actions的关系是?
现有项目如何集成vuex
下载vuex
创建store对象并注入到Vue实例中
定义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中的数据
}
}
把vuex商品数组映射回来使用
网络请求调用actions方法
mapState可以改变映射到原地的计算属性名吗?
可以的, 格式…mapState({''计算属性名', 'state里要映射的变量名'})
vuex身上的全局状态-计算属性, 类似于computed
getters 依赖于 state中原始数据的变化,并返回计算后的新数据
在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
方式1: 组件内 - 直接使用
语法:
this.$store.getters.计算属性名
方式2: 组件内 - 映射使用
语法:
// 1. 拿到mapGetters辅助函数
import { mapGetters } from 'vuex'
export default {
computed: {
// 2. 把getters里属性映射到原地
...mapGetters(['getters里的计算属性名'])
}
}
使用2种方式给计算属性值
getters如何使用?
方式1: this.$store.getters.计算属性名
方式2: …mapGetters(['getters里计算属性名'])
语法: 对象里包含5个核心概念, 只有state变成函数形式
user.js - 用户模块对象
// 用户模块对象
const userModule = {
state(){
return {
name: "",
age: 0,
sex: ''
}
},
mutations: {},
actions: {},
getters: {}
}
export default userModule
cart.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: {
模块名: 模块对象
}
把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里 { 模块名: 模块对象 }
方式1: 组件内 - 直接使用
原语法:
this.$store.state.变量名
分模块后语法:
this.$store.state.模块名.变量名
方式2: 组件内 - 映射使用
原语法:
...mapState(['state里变量名'])
...mapState({'变量名': "state里变量名"}) // 给映射过来的state起别的名字
分模块后语法:
...mapState({
'变量名': state => state.模块名.变量名
})
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.模块名.变量名
在模块对象内设置namespaced: true
const moduleShopCar = {
namespaced: true,
state () {},
mutations: {},
actions: {},
getters: {},
modules: {}
}
直接使用无变化: this.$store.state.模块名.变量名
辅助函数需要遵守格式
...mapState("模块名", ['state变量名'])
方式1: 组件内 - 直接使用
原语法:
this.$store.commit("mutations里的函数名", 具体值)
开命名空间后语法:
this.$store.commit("模块名/mutations里的函数名", 具体值)
方式2: 组件内 - 映射使用
原语法:
...mapMutations(['mutations里方法名'])
开命名空间后语法:
...mapMutations("模块名", ['mutations里方法名'])
方式1: 组件内 - 直接使用
原语法:
this.$store.dispatch("actions里的函数名", 具体值)
开命名空间后语法:
this.$store.dispatch("模块名/actions里的函数名", 具体值)
方式2: 组件内 - 映射使用
原语法:
...mapActions(['actions里方法名'])
开命名空间后语法:
...mapActions("模块名", ['actions里方法名'])
方式1: 组件内 - 直接使用
原语法:
this.$store.getters.计算属性名
开命名空间后语法:
this.$store.getters['模块名/计算属性名']
方式2: 组件内 - 映射使用
原语法:
...mapGetters(['getters里计算属性名'])
开命名空间后语法:
...mapGetters("模块名", ['getters里计算属性名'])
state和mutations, 在根store和开启命名空间里的区别?
整个vuex的体系是?
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的state、mutations、actions、getters、甚至是嵌套子模块。
默认情况下,模块内部的 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'])
}
}
优秀的调试工具可以使我们写程序事半功倍,最后我们再学习一下如果使用dev-tools来调试vuex中的数据,这也是数据可预测特性里不可缺少的一环
注意只有vue+vuex的项目才可以用
手机扫一扫
移动阅读更方便
你可能感兴趣的文章