1、什么是vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
我们是否可以通过组件数据的传递来完成呢?
- 对于一些简单的状态,确实可以通过props的传递或者Provide的方式来共享状态;
- 但是对于复杂的状态管理来说,显然单纯通过传递和共享的方式是不足以解决问题的,比如兄弟组件如何共享 数据呢?
1.这个状态自管理应用包含以下几个部分:
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
Vuex 是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享。
2. 使用Vuex 统一管理状态的好处
① 能够在 vuex 中集中管理共享的数据,易于开发和后期维护
② 能够高效地实现组件之间的数据共享,提高开发效率
③ 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步
什么样的数据适合存储到 Vuex 中
一般情况下,只有组件之间共享的数据,才有必要存储到 vuex 中;对于组件中的私有数据,依旧存储在组件
自身的 data 中即可。
Vuex和单纯的全局对象有什么区别呢?
第一:Vuex的状态存储是响应式的
当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新;
第二:你不能直接改变store中的状态
改变store中的状态的唯一途径就显示提交 (commit) mutation;
这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态;
3. 五个核心属性
(1)state
- 可以理解为组件当中的data
- 组件当中的data 是一个局部的数据仓库
- state 是一个全局的数据仓库 只要数据存放到state之后 任何组件都能访问state里面的属性
- 把组件当中需要被共享的属性存放到vuex当中
(2)mutations
-
mutations 可以理解为全局的方法 跟组件当中的methods类似
-
定义在组件当中的methods是局部方法
-
定义在mutations里面的方法 可以被任何组件所访问
-
如果想要state当中的数据变化 需要在mutations里面修改 这是一个规范
-
载荷
通俗的理解就是传递的参数
this.$store.commit(“upts”,{name:”赵六”,count:2}); -
在vuex的mutations当中 传递的载荷只能传递一个
如果想要传递多个载荷的化 可以把传递的参数合并成数组或者对象
(3)getters
- 类似与computed
- 用于监听vuex state数据的变化 用法跟计算属性的用法一致
(4)actions
- 可以理解为全局的方法 跟组件当中的methods类似
- mutations里面写的方法是一个同步方法
- actions 里面写的方法 可以是同步 也可以是异步
- 通过actions 解决了 mutations异步操作导致的 调试工具与页面所展示的数据不一致的问题
- 把异步操作写在actions 当中 在actions里面执行mutations里面的方法
(5)modules
模块或者分组,如果项目很大,要vuex管理的状态很多,都写到一个文件有点臃肿,这个时候分不同的模块,单个文件就非常小,而且单一集中更好管理。一般情况下都写在index.js中就够了。
2.使用vuex
在vue3.x中使用vuex的方法与vue2.x中使用vuex的方法是不同的
1.安装以及初始化
在vue3中使用vuex的版本要在4版本以上
npm install vuex@next
or
cnpm install vuex@4
vuex4 初始化方式做了相应的变化,使用新的 createStore 函数创建新的 store 实例。
//vuex全局管理文件
import { createStore } from "vuex";
const store = createStore({
state: {
count: 100,
movie: '',
price: "45元",
str: '我是共享数据',
count: 10,
movie01: '花束般的恋爱',
movie02: '狙击手',
movie03: '奇迹笨小孩',
movie04: '我们的冬奥',
movie05: {
title: '这个杀手不太冷静',
price:50
},
num: 0,
list: [],
books:['西游记','水浒传','三国演义','人性的弱点','你当像鸟飞向那座山',"晚熟的人","人类简史","今日简史","未来简史","三体"],
},
mutations: {
},
actions: {
},
getters: {
}
})
export default store;
//main.js
//store的注册
//直接在main.js里面使用app.use()进行安装即可。
import { createApp } from 'vue'
import App from './App.vue'
import router from "./router"
import store from "./store"
createApp(App).use(router).use(store).mount('#app');
2.state的使用
<template lang="">
<div>
<h3>获取state数据</h3>
<p>1.直接在页面上获取:{{$store.state.count}}</p>
<p>2.普通方式获取state数据---{{getCount}}---{{movie01}}</p>
<p>3.mapState获取state数据:{{count}}---{{movie02}}---{{movie03}}</p>
</div>
</template>
<script setup>
import { computed} from "vue"
import {useStore,mapState } from "vuex";
const store=useStore();
//普通的方式获取state
const getCount=computed(()=>store.state.count);
const movie01=computed(()=>store.state.movie01);
//映射函数的方式获取state
const state={}
const fnState=mapState(["count","movie02","movie03"]);
Object.keys(fnState).forEach(fnKey=>{
const fn=fnState[fnKey].bind({$store:store})
state[fnKey]=computed(fn)
})
const {count,movie02,movie03}=state;
</script>
默认情况下,Vuex并没有提供非常方便的使用mapState的方式,这里我们进行了一个函数的封装:
上面那种在setup函数里面写一大坨代码不是我们想要的效果。所以我们进行简单的函数封装
我们可以把映射函数mapState封装一下,使用会更加的方便:
先新建一个useState.js文件:
import { mapState, useStore } from "vuex";
import { computed } from "vue";
export const useMapState = (getKeys) => {
const store = useStore();
const storeState = {};
const storeFns = mapState(getKeys);
Object.keys(storeFns).forEach((fnkeys) => {
const fn = storeFns[fnkeys].bind({ $store: store });
storeState[fnkeys] = computed(fn);
})
return storeState;
}
bind方法的作用,就可以把相关的语法糖绑定,最终返回一个动态的ref或者reactive.
然后在组件中引入:
import {useMapState} from "../utils/useState"
<script setup>
const {count,accessToken,rolename}=useMapState(["count","accessToken","rolename"])
</script>
不在setup语法糖中的使用
<script>
import { useMapState } from "@/utils/useState"
export default {
setup(props) {
const states=useMapState(["count","movie01","movie02"]);
return {
...states
}
}
}
</script>
3.mutations的使用
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation:Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
<template>
<div>
<h3>使用mutations</h3>
<p> 1.普通方式提交mutations:<button @click="add">add count--{{count}}</button> </p>
<p>2.映射函数mapMutations: <button @click="getCount(100)">getCount --{{count}}</button> </p>
</div>
</template>
<script setup>
import { useStore,mapMutations } from 'vuex';
import { computed } from 'vue';
const store=useStore();
const count=computed(()=>store.state.count);
const add=()=>{
store.commit("addCount",10);
}
const getCount=mapMutations(["addCount"]).addCount.bind({$store:store});
</script>
如果不是在setup语法糖中:mapMutations返回的对象里面的属性值就刚好是函数,不需要再次封装,直接解构即可使用
export default {
setup(props) {
const storeMutations=mapMutations({
getCount:'addCount',
getMovie:"getMovie"
})
return {
...storeMutations
}
}
}
4.actions
Action类似于mutation,不同在于:
Action提交的是mutation,而不是直接变更状态;
Action可以包含任意异步操作;
这里有一个非常重要的参数context:
context是一个和store实例均有相同方法和属性的context对象;
所以我们可以从其中获取到commit方法来提交一个mutation,或者通过 context.state 和 context.getters 来 获取 state 和 getters;
但是为什么它不是store对象呢?这个等到我们讲Modules时再具体来说;
<template>
<div>
<h3>分发actions</h3>
<p>1.普通方式分发actions: <button @click="getList">分发actions</button> </p>
<p>2.映射方式分发actions: <button @click="getMovieList">分发actions</button> </p>
</div>
</template>
<script>
import { useStore,mapActions} from "vuex"
export default {
setup(props) {
var store=useStore();
const getList=()=>{
store.dispatch("asyncGetList");
}
const storeActions=mapActions({
getMovieList:"asyncGetList"
})
return {
getList,
...storeActions
}
}
}
</script>
在setup语法糖中的用法:
<script setup>
import { useStore,mapActions} from "vuex"
var store=useStore();
const getList=()=>{
store.dispatch("asyncGetList");
}
const getMovieList=mapActions(["asyncGetList"]).asyncGetList.bind({$store:store});
</script>
5.getters用法
某些属性我们可能需要经过变化后来使用,这个时候可以使用getters:
<template>
<div>
<h3>getters</h3>
<p>1.普通的方式获取getters:-----{{disCurrent}}</p>
<p>2.映射函数的方式获取getters:---{{currentDiscount}}---{{total}}</p>
</div>
</template>
<script setup>
import { useStore,mapGetters } from 'vuex';
import { computed } from 'vue';
//引入封装好的映射函数
import {useMapGetters} from "../utils/useGetters"
const store=useStore();
//1.普通方式获取getters
//获取当前的折扣
const disCurrent=computed(()=>store.getters.currentDiscount);
//2.映射函数的方式获取getters
/**
const getters={};
const storeGetters=mapGetters(["currentDiscount","total"]);
Object.keys(storeGetters).forEach(fnkey=>{
getters[fnkey]=computed(storeGetters[fnkey].bind({$store:store}))
})
const {currentDiscount,total}=getters;
*/
//使用封装好的方法
const {currentDiscount,total} =useMapGetters(["currentDiscount","total"]);
</script>
<style lang="scss">
</style>
在Vue3中,我们常常在Composition Api中使用到vuex的mapState和mapGetters,因为每次获取很麻烦所以就封装了他们,支持模块命名,一键使用,轻松上手
主要实现函数:useMapper.js
import { computed } from "vue"
import { useStore } from "vuex"
// 组合mapState和mapGetters
export default function(data, mapFn) {
const store = useStore()
const storeGettersFns = mapFn(data)
const storeGetters = {}
Object.keys(storeGettersFns).forEach( fnkey => {
const fn = storeGettersFns[fnkey].bind({$store: store})
storeGetters[fnkey] = computed(fn)
})
return storeGetters
}
两个供调用的函数: useState.js 和 useGetters.js
useGetters.js
import { mapGetters, createNamespacedHelpers } from "vuex"
import useMapper from "./useMapper"
export default function (name, getters) {
let mapFn = mapGetters
if (typeof name === 'string' && name.length > 0) {
mapFn = createNamespacedHelpers(name).mapGetters
}
return useMapper(getters, mapFn)
}
useState.js
import { mapState, createNamespacedHelpers } from "vuex"
import useMapper from "./useMapper"
export default function (name, states) {
let mapFn = mapState
if(typeof name === 'string' && name.length > 0 ) {
mapFn = createNamespacedHelpers(name).mapState
}
return useMapper(states, mapFn)
}
使用
写法一:
import useState from "../../hooks/useMapState"
import useGetters from "../../hooks/useGetters"
export default {
setup(){
const mapStoreState = useState(['name', 'age', 'gender'])
const mapStoreGetters = useGetters(['fullName'])
return {
...mapStoreState,
...mapStoreGetters
}
},
}
写法二: 如果store使用了模块定义的,用这种写法
import useState from "../../hooks/useMapState"
import useGetters from "../../hooks/useGetters"
export default {
setup(){
const mapStoreState = useState('user', ['name', 'age', 'gender'])
const mapStoreGetters = useGetters('user', ['fullName'])
return {
...mapStoreState,
...mapStoreGetters
}
},
}