vuex状态管理
简单介绍
在开发中,我们会的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就称之为是状态管理。
在不使用 vuex 时,管理状态方式:
- 在 vue 开发中,我们使用组件化的开发方式。
- 在组件中我们定义 data 或 setup 中返回使用的数据,这些数据为 state。
- 在模块 template 中我们可以使用这些数据,模块会最终渲染成 DOM,成为 view。
- 在模块中产生一些行为事件,可能会修改state,我们称为 actions
快速开始
安装
npm install vuex
创建 store/index.js
import { createStore } from 'vuex';
const store = createStore({
state: () => ({
counter: 0
})
})
export default store
在 main.js 中使用 store
import { createApp } from 'vue'
import App from './App.vue'
// 注册并使用 store
import store from './store/index'
createApp(App).use(store).mount('#app')
在 template 中拿到 状态值
<template>
<div class="home">
<h2>Home计数: {{ $store.state.counter }}</h2>
</div>
</template>
修改 store 中的状态
使用 commit-mutation。在index.js中使用mutations 字段配置 mutations, 然后在vue组件中使用 useStore 使用状态。
// 在 composition API 中
// ./store/index.js
const store = createStore({
state: () => ({
counter: 0
}),
mutations: {
increment(state) {
state.counter++;
}
}
})
// Home.vue
import { useStore } from 'vuex';
const store = useStore();
function increment() {
store.commit('increment');
}
在 OptionApi 中使用
<script>
// 在options API 中 computed中
export default {
computed: {
storeCounter() {
return this.$store.state.counter;
}
}
}
</script>
单一状态树
-
VueX 使用单一状态树
- 用一个对象就包含了全部应用层级的状态
- 采用的是 SSOT, Single Source of Truth, 单一数据源
-
一个应用程序仅仅包含一个 store 实例
- 单一状态树与模块化不冲突。
mapState - state 映射
<template>
<div class="home">
<h2>name: {{ $store.state.name }}</h2>
<h2>age: {{ $store.state.age }}</h2>
<hr>
<h2>name: {{ name }}</h2>
<h2>age: {{ age }}</h2>
<hr>
<h2>name: {{ sName }}</h2>
<h2>age: {{ sAge }}</h2>
</div>
</template>
optionApi 中的 mapState
方式一:数组类型
// 映射函数
...mapState(['name', 'age']),
方式二:对象类型
...mapState({
sName: state => state.name,
sAge: state => state.age
})
compositionApi 中的 mapState
import { computed, toRefs } from 'vue';
import { mapState, useState } from 'vuex';
// 正常使用
const { name, age } = mapState();
const store = useStore();
const cName = computed(name.bind({ $store: store }));
const cAge = computed(age.bind({ $store: store }));
// 直接对 store.state 进行解构
const store = useStore();
const { name, age } = toRefs(store.state);
封装 useState.js
// 封装 mapState、useStore()
import { computed } from 'vue';
import { useStore, mapState } from 'vuex'
export default function useState(mapper) {
const store = useStore();
const stateFnsObj = mapState(mapper);
const newState = {};
Object.keys(stateFnsObj).forEach(key => {
newState[key] = computed(stateFnsObj[key].bind({ $store: store }));
})
return newState;
}
使用 useState.js
// 解构 state
const { name, age } = useState(['name', 'age']);
getters 字段
vuex 给予 state 的计算属性,类似于 computed.起到过滤数据的作用
const store = createStore({
state: () => ({
counter: 100,
name: 'codewhy',
age: 12,
users: [
{ id: 111, name: 'why', age: 20 },
{ id: 112, name: 'kobe', age: 30 },
{ id: 113, name: 'james', age: 21 }
]
}),
// getters 字段
getters: {
doubleCounter(state) {
return state.counter * 2;
},
totalAge(state) {
return state.users.reduce((preValue, item) => {
return preValue + item.age;
}, 0);
},
message(state, getters) {
// 在 getter 中拿到别的 getter
return `name: ${state.name} age: ${state.age} totalAge: ${getters.totalAge}`;
},
getFriend(state) {
return function (id) {
return state.users.find(item => item.id === id)
}
}
},
mutations: {
increment(state) {
state.counter++;
}
}
})
在组件中使用
<template>
<div class="home">
<!-- <button @click="incrementAge">+1</button> -->
<h2>doubleCounter: {{ $store.getters.doubleCounter }}</h2>
<h2>totalAge: {{ $store.getters.totalAge }}</h2>
<h2>message: {{ $store.getters.message }}</h2>
<h2>111的信息: {{ $store.getters.getFriend(112) }}</h2>
</div>
</template>
mapGetters - getters 映射
optionApi 中的 mapGetters
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['doubleCounter', 'totalAge']),
...mapGetters(['getFriend'])
}
}
</script>
compositionApi 中的 mapGetters
<script setup>
import { computed, toRefs } from 'vue';
import { mapGetters, useStore } from 'vuex';
const store = useStore();
// 基本使用,方式一
// const { message: messageFn } = mapGetters(['message'])
// const message = computed(messageFn.bind({ $store: store }));
// const message = computed(() => store.getters.message);
// 解构使用,方式二
const { message } = toRefs(store.getters);
function changeName() {
store.state.name = 'kobe';
}
</script>
mutations 字段
修改 state 的唯一方法
const store = createStore({
state: () => ({
counter: 100,
name: 'codewhy',
age: 12,
users: [
{ id: 111, name: 'why', age: 20 },
{ id: 112, name: 'kobe', age: 30 },
{ id: 113, name: 'james', age: 21 }
]
}),
// mutations 字段
mutations: {
increment(state) {
state.counter++;
},
// mutation 的第二参数
changeName(state, payload) {
state.name = payload
},
incrementAge(state, payload) {
state.age++;
}
}
})
在 optionApi 中使用,使用 使用 commit
<script>
export default {
computed: {
},
methods: {
changeName() {
console.log('修改name');
// this.$store.state.name = 'hai';
this.$store.commit('changeName', '王五');
},
incrementAge() {
this.$store.commit('incrementAge');
}
}
}
</script>
定义 mutation-type.js 常量类型
export const INCREMENT_AGE = "incrementAge"
在 store 中引入使用
// mutations 字段
mutations: {
increment(state) {
state.counter++;
},
changeName(state, payload) {
state.name = payload
},
// 使用
[INCREMENT_AGE](state, payload) {
state.age++;
}
}
在组件中也统一使用
<script>
import { INCREMENT_AGE } from '../store/mutations'
export default {
computed: {
},
methods: {
changeName() {
console.log('修改name');
// this.$store.state.name = 'hai';
this.$store.commit('changeName', '王五');
},
// 统一使用
[INCREMENT_AGE]() {
this.$store.commit('incrementAge');
}
}
}
</script>
optionApi 中的 mapMutations
<script>
import { mapMutations } from 'vuex';
import { INCREMENT_AGE } from '../store/mutations'
export default {
computed: {
},
methods: {
btnClick() {
console.log("btnClick")
},
...mapMutations(["increment", "changeName", INCREMENT_AGE])
}
}
</script>
compositionApi 中的 mapMutations
<script setup>
import { useStore, mapMutations } from 'vuex';
import { INCREMENT_AGE } from '../store/mutations'
const store = useStore();
const mutations = mapMutations(["changeName", INCREMENT_AGE]);
const newMutations = {};
Object.keys(mutations).forEach(key => {
newMutations[key] = mutations[key].bind({ $store: store });
})
const { incrementAge, changeName } = newMutations;
</script>
注意:mutations 中必须为同步代码。
actions 字段
action 类似于 mutattion, 但 action 提交 mutation, 而不直接改变状态。action 可以包含异步操作。
- 包含一个 context 参数
- context 与 store 具有相同的实例属性与方法。
- 从 context 获取一个 commit 方法来提交 mutation,也可以通过 context.state 和 context.getters 来获取 state 和 getters
const store = createStore({
state: () => ({
counter: 100,
name: 'codewhy',
age: 12,
users: [
{ id: 111, name: 'why', age: 20 },
{ id: 112, name: 'kobe', age: 30 },
{ id: 113, name: 'james', age: 21 }
]
}),
mutations: {
increment(state) {
state.counter++;
},
},
actions: {
incrementAction(context) {
context.commit('increment');
}
}
})
在 optionApi 中使用 dispatch函数调用 incrementAction
<script>
export default {
methods: {
actionBtnClick() {
this.$store.dispatch("incrementAction");
}
}
}
</script>
optionsApi 中的 mapActions
<script>
import { mapActions } from 'vuex';
export default {
methods: {
// actionBtnClick() {
// this.$store.dispatch("incrementAction");
// },
// changeName() {
// this.$store.dispatch("changeNameAction", "aaa");
// }
...mapActions(['incrementAction', 'changeNameAction'])
}
}
</script>
compositionApi 中的 mapActions
<script setup>
import { useStore, mapActions } from 'vuex'
const store = useStore();
const actions = mapActions(['incrementAction', 'changeNameAction']);
const newActions = {};
Object.keys(actions).forEach(key => {
newActions[key] = actions[key].bind({ $store: store });
})
const { incrementAction, changeNameAction } = newActions;
// 默认做法,派发事件
function increment() {
store.dispatch('incrementAction');
}
</script>
actions 异步操作
基本步骤:在组件中发起请求派发 -> 在actions发起真正的请求 -> 将请求的数据存入 state -> 再在组件 template 中使用 state;
<script setup>
import { useStore } from 'vuex';
// 告诉 vuex 发起请求
const store = useStore();
store.dispatch('fetchHomeMultidataAction')
</script>
actions 中发起请求
const store = createStore({
state: () => ({
// 模拟数据
counter: 100,
name: 'codewhy',
age: 12,
users: [
{ id: 111, name: 'why', age: 20 },
{ id: 112, name: 'kobe', age: 30 },
{ id: 113, name: 'james', age: 21 }
],
// server 数据
banners: [],
recommends: []
}),
getters: {},
mutations: {
changeBanners(state, banners) {
state.banners = banners;
},
changeRecommends(state, recommends) {
state.recommends = recommends;
}
},
actions: {
async fetchHomeMultidataAction(context) {
// fetch("http://123.207.32.32:8000/home/multidata").then(res => {
// res.json().then(data => {
// console.log(data);
// })
// })
// fetch("http://123.207.32.32:8000/home/multidata").then(res => {
// return res.json();
// }).then(data => {
// console.log(data);
// })
// await async
const res = await fetch("http://123.207.32.32:8000/home/multidata");
const data = await res.json();
console.log(data);
// 存入 state
context.commit("changeBanners", data.data.banner.list);
context.commit("changeRecommends", data.data.recommend.list);
}
}
})
在 组件模板中使用
<template>
<div class="home">
<h2>Home Page</h2>
<ul>
<template v-for="item in $store.state.banners" :key="item.acm">
<li>{{ item.title }}</li>
</template>
</ul>
</div>
</template>
Modules-模块
- 由于使用单一状态树,应用的所有状态会集中到一一个比较大的对象,当应用变得非常复杂时,store 对象就有可能变得相当臃肿
- 为了解决以上问题,Vuex 允许我们将store分割成模块(module) ;
- 每个模块拥有自己的state、mutation. action、 getter、 甚至是嵌套子模块; .
const moduleA = {
state: () => ({}),
mutations: {},
getters: {},
actions: {}
}
const store = createStore({
modules: {
module_A: moduleA
}
})
module命名空间
- 默认情况下,模块内部的 actions 和 mutations 仍然是注册在全局命名空间中。
- 使用
namespace: true;
可以让模块拥有自己命名空间
const counter = {
namespaced: true,
state: () => ({
counter: 99
}),
mutations: {
incrementCount(state) {
state.counter++;
}
},
getters: {
doubleCount(state, getters, rootState) {
return state.counter + rootState.rootCounter;
}
},
actions: {
incrementCountAction(context) {
context.commit("incrementCount");
}
}
}
在 template 中使用时
<template>
<div class="home">
<h2>Home Page</h2>
<!-- 使用 state 时,需要 state.moduleName.xxx -->
<h2>Counter模块的counter: {{ $store.state.counter.counter }}</h2>
<!-- 使用 getters 时,直接 state.xxx -->
<h2>Counter模块的doubleCounter: {{ $store.getters["counter/doubleCount"] }}</h2>
<button @click="incrementCount">counter模块+1</button>
</div>
</template>