Vue 作为一个渐进式的框架,在学习完主体的部分后,我们可以在用到某个技术的时候再去学别的技术,比如今天的 Vuex 就是一个管理全局数据的技术,主要是要掌握 Vuex 的五大核心,希望这篇文章结合官方文档可以帮助大家更快的掌握 Vuex 的基本使用
学习一个工具之前先要了解它能为我们解决问题或者使用它有什么优势
Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
状态 就是我们说的 数据,可以帮助我们数据的全局共享,所以一般用来管理一些通用的数据,比如 个人登录信息数据 等。
什么是状态管理模式呢?
比如说我们有一个计数器,点击 +
使得数字自增,-
相反,那这个简单的应用包含哪些部分呢?
这是单一组件的情况,但如果出现多个组件共享状态的时候,就会出现多个视图依赖于统一个状态,多个行为又要变更同一个状态,这样代码写起来就会过于混乱和复杂。
所以考虑到把组件的共享状态单独 抽取 出来,使得组件树的任何地方都能获取状态和通过行为改变状态。
使用 Vuex 可以实现数据的 集中化管理,并且由于其内部提供的辅助函数,操作简洁。
并不是所有情况都需要用到 Vuex,我们来看一下官方的解释
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发 大型单页 应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:
Flux 架构就像眼镜:您自会知道什么时候需要它。
因为我们目前使用 Vue CLI
架子来初始化项目的时候选中了vuex
就不需要再手动导入了,但是如果没有选择 就需要额外安装:
vue3
yarn add vuex@next --save 或者 npm install vuex@next --save
然后我们来新建一个 stroe
目录来放置一个 index.js
文件,后面我们基于 Vue CLI 选单中 vuex
后生成的初始化项目来讲述。
我们在 WebStrom
中打开项目
每一个 Vuex 应用的核心就是 store(仓库)。stor”是一个容器,它包含着你的应用中大部分的状态 (state)。
新建仓库(store/index.js
)
import { createStore } from "vuex";
export default createStore({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {},
});
将 store
对象作为插件安装(main.js
)
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
这样就完成了一个仓库的创建,也是 Vue CLI 初始化帮助我们搭建好的模板。
创建完仓库后,我们就要考虑如何给仓库 提供 数据和如何 使用 仓库中的数据了
我们可以向参控股中的 state
对象去添加一份数据
import { createStore } from 'vuex'
export default createStore({
// 通过 state 可以提供数据
state: {
count: 101
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
比如上面的情况,我们就向里面添加了一个 count
数据
store
来直接访问获取 store
的两种方式
this.store
import store from "@/store/index";
导入后应该如何使用呢?
再模板中: {{ $store.state.xxx }}
在组件函数中: this.$store.state.xxx
在 JS 模块中: store.state.xxx
比如我们可以通过上面的方式在 App.vue
组件中去访问到上面的数据:
App.vue
<template>
<h1>{{store.state.count }}</h1>
</template>
<script>
import store from "@/store/index";
export default {
name: 'App',
data() {
return {store}
},
components: {}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
通过 mapState
函数将 store
中的数据自动映射到组件的 计算属性 中
import {mapState} from ;vuex
state
mapState(['count'])
数组中写需要的属性...mapState(['count'])
得到的是一个对象,对象中包含着计算属性,我们返回的结果赋值给 computed
即可,但是出现的问题就是我们的 computed
直接就被占满了,如果我们还想添加新的计算属性就没法操作了,所以我们就把这个数据展开最终的代码就是这样的
<template>
<h1>{{ count }}</h1>
</template>
<script>
import store from "@/store/index";
// 引入
import {mapState} from "vuex";
export default {
name: 'App',
data() {
return {store}
},
computed:
{
// 展开运算符
...mapState(['count'])
}
}
</script>
vuex 中同样遵循单向数据流,组件中不能直接修改仓库中的数据,比如我们试一下
<template>
<h1>{{ count }}</h1>
<button @click="handleAdd">+</button>
</template>
<script>
import store from "@/store/index";
import {mapState} from "vuex";
console.log(mapState(['count', 'title']))
export default {
name: 'App',
methods: {
handleAdd() {
this.count++;
}
},
computed:
{
...mapState(['count'])
}
}
</script>
这里我们注册了一个按钮来使得数据自增,经过试验是无法操作的,试想一下,如果任意位置都能修改仓库的数据,那我们后续去维护的时候,就很难找到数据是在哪个组件去修改的,所以需要用到 mutations
.
在 store
定义的位置定义一个 mutations
对象,内部存放修改 state
的方法
mutations: {
addCount (state) {
state.count += 1;
}
}
组件中提交调用 mutations
this.$store.commit('addCount')
下面给出代码示例
index.js
import { createStore } from 'vuex'
export default createStore({
// 通过 state 可以提供数据
state: {
count: 101,
title: '仓库标题'
},
getters: {
},
// 提供修改数据的方法
mutations: {
addOne(state) {
state.count += 1;
}
},
actions: {
},
modules: {
}
})
App.xue
methods: {
handleAdd() {
// 需要提交调用 mutations
this.$store.commit('addOne');
}
},
这样就是实现了操控 store
中的内容
如果我们想要实现自己控制加的数字大小,只需要传参的时候传入需要加的参数即可,mutations 也提供了传参的语法
提供 mutation 函数,带参数的
mutations: {
addCount (state, 参数) {
state.count += 参数;
}
}
页面中提交调用 mutation
this.$store.commit('addCount', 参数)
下面给出代码示例
index.js
import { createStore } from 'vuex'
export default createStore({
// 通过 state 可以提供数据
state: {
count: 101,
title: '仓库标题'
},
getters: {
},
// 提供修改数据的方法
mutations: {
addOne(state, n) {
state.count += n;
}
},
actions: {
},
modules: {
}
})
App.xue
methods: {
handleAdd() {
// 需要提交调用 mutations
this.$store.commit('addOne', 10);
}
},
这个函数和前面的 mapstate
很像,它是把位于mutataions
中的方法提取出来,映射到组件 methods
中,这里直接给出示例代码:
<template>
<h1>{{ count }}</h1>
<button @click="addNum(10)">+</button>
</template>
<script>
import {mapState} from "vuex";
import {mapMutations} from "vuex";
console.log(mapState(['count', 'title']))
export default {
name: 'App',
// 引入方法
methods: {
...mapMutations(['addNum'])
},
computed:
{
...mapState(['count'])
}
}
</script>
action 是为了让我们可以异步的操控这个数据,之前学到的 mutations
中的方法为了监测必须是同步的,所以如果需要异步的方法就需要 actions
actions
方法actions: {
setAsyncCount (content, num) {
setTimeout(() => {
context.commit('changeCount', num);
}, 1000);
}
}
可以看到, actions
中的方法仍然使用了 commit
去修改,只是可以把异步的代码写到里面
dispatch
调用this.$store.dispatch('setAsyncCount', 200);
和上面的 mapMutations 非常类似,是吧位于 actions
中的方法提取出来,映射到组件 methods
中.
index.js
import {createStore} from 'vuex'
export default createStore({
// 通过 state 可以提供数据
state: {
count: 101,
title: '仓库标题'
},
getters: {},
// 提供修改数据的方法
mutations: {
addNum(state, n) {
state.count += n;
}
},
actions: {
changeNum(context, num) {
setTimeout(() => {
context.commit('addNum', num)
}, 1000);
}
},
modules: {}
})
App.vue
<template>
<h1>{{ count }}</h1>
<button @click="addNum(10)">+</button>
<button @click="changeNum(10)">一秒后加</button>
</template>
<script>
import {mapActions, mapState} from "vuex";
import {mapMutations} from "vuex";
console.log(mapState(['count', 'title']))
export default {
name: 'App',
// 引入方法
methods: {
...mapMutations(['addNum']),
...mapActions(['changeNum'])
},
computed:
{
...mapState(['count'])
}
}
</script>
除了 state
以外,我们还可能需要用到基于 state
中的内容的属性的新属性(和前面学到的计算属性非常类似).
定义 getters: 下面演示了 state
中的 list
数组中保存数据,我们提取出其中大于 5
的部分作为计算属性
getters: {
filterList (state) {
return state.list.filter(item => item > 5)
}
}
访问 getters
$store.getters.filterList
同样的,也可以通过 mapGetters 映射
computer: {
...mapGetters(['filterList'])
}
由于 vuex 使用单一状态树,所有的状态都会集合到这个很大的 state
对象中,如果要存储的内容非常多,这个对象就会非常臃肿,可维护性和可读性都非常差,所以为什么不分模块呢?
那拆分成模块需要哪些步骤呢?
store
下面新建一个 modules
文件夹js
代码,每个代码就代表一个模块store
类似user.js
const state = {
userInfo: {
name: 'Tom',
age: 18
}
}
const mutations = {}
const actions = {}
const getters = {}
export default {
state,
mutations,
actions,
getters
}
index.js
import {createStore} from 'vuex'
import user from "./modules/user";
import setting from "./modules/setting";
export default createStore({
// 通过 state 可以提供数据
state: {
count: 101,
title: '仓库标题'
},
getters: {},
// 提供修改数据的方法
mutations: {
addNum(state, n) {
state.count += n;
}
},
actions: {
changeNum(context, num) {
setTimeout(() => {
context.commit('addNum', num)
}, 1000);
}
},
modules: {
user,
setting
}
})
注意看上面的 modules
部分
虽然已经分模块了,但本质上还是挂载到跟模块上的,但是管理和维护起来变得非常容易了
下面给出如何访问子模块中的数据,因为和前面非常类似,就不写案例了
使用子模块中的数据:
mapState
映射:
获取 getter 数据:
使用模块中 getters
中的数据:
$store.getters['模块名/xxx ']
mapGetters
映射
mapGetters([ 'xxx' ])
mapGetters('模块名', ['xxx'])
- 需要开启命名空间获取 mutations 方法:
store
调用 $store.commit('模块名/xxx ', 额外参数)mapMutations
映射
获取 actions 方法:
store
调用 $store.dispatch('模块名/xxx ', 额外参数)mapActions
映射
开启命名空间:
写在子模块的导出语句中:
const state = {
userInfo: {
name: 'Tom',
age: 18
}
}
const mutations = {}
const actions = {}
const getters = {}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}