一、Vuex 概述
1. Vuex 是什么
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
我们可以理解为:它是一个Vue插件工具,它可以解决不同组件数据共享问题
在以前我们是如何管理自己的状态呢?
- 在组件中我们定义data或者在setup中返回使用的数据,这些数据我们称之为 state
- 在模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为 view
- 在模块中我们会产生一些事件,为了处理这些事件,有可能会修改state,这种操作我们称之为 actions
这三个就对应了vuex的三个核心概念:state、mutations、actions
2. Vuex 好处与用处
使用 Vuex 统一管理状态的好处
- 能够在 vuex 中集中管理共享的数据,易于开发和后期维护
- 能够高效地实现组件之间的数据共享,提高开发效率
- 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步
什么样的数据适合存储到 Vuex 中
- 一般情况下,只有需要在组件之间共享的数据,才有必要存储到 vuex 中
- 对于组件中的私有数据,依旧存储在组件自身的 data 中即可
二、Vuex 的核心概念
Vuex 应用的核心就是 store(仓库)。store 基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。
Vuex和单纯的全局对象有什么区别呢?
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
看图结论:
- state:管理数据,管理的数据是响应式的,当数据改变时驱动视图更新。
- mutations:更新数据,state中的数据只能使用mutations去改变数据。
- actions:请求数据,响应成功后把数据提交给mutations
三、Vuex 的基本使用
1. 安装配置 Vuex
1、安装 vuex 依赖包:
npm i vuex@4 -S
2、创建 store/index.js,并编写下列示例代码
// 1. 导入
import { createStore } from 'vuex'
// 2. 创建一个新的 store 实例
const store = createStore({
state: {},
mutations: {},
actions: {},
modules: {}
})
// 3. 默认导出
export default store
3、在 main.js 中配置 store
import { createApp } from 'vue'
import App from './App.vue'
// 导入 store
import store from './store'
const app = createApp(App)
// 将 store 实例作为插件安装
app.use(store)
app.mount('#app')
总结: 和 vue-router 的使用方式一样。初始化,编写规则,然后再main.js中使用
2. state
state 提供唯一的公共数据源 ,所有共享的数据都要统一放到 Store 的 state 中进行存储
2.1 定义数据
const store = createStore({
state: {
// 表示state需要管理的数据
count: 0
}
})
2.2 使用数据
1、原始形式,组件中可以使用 this.$store 获取到的store对象,再通过 state 属性获取 count, 如下
<template>
<div>state的数据:{{ this.$store.state.count }}</div>
</template>
2、计算属性,将 state 属性定义在计算属性中
<template>
<div>state的数据:{{ this.$store.state.count }}</div>
<div>state的数据:{{ count }}</div>
</template>
<script>
export default {
// 把state中数据,定义在组件内的计算属性中
computed: {
count () {
return this.$store.state.count
}
}
}
</script>
3、辅助函数,mapState
mapState是辅助函数,帮助我们把store中的数据映射到 组件的计算属性中, 它属于一种方便用法
- 导入 mapState 函数,然后使用延展运算符将导出的状态映射给计算属性
// 1. 从 vuex 中按需导入 mapState 函数
import { mapState } from 'vuex'
export default {
// 2. 将全局数据,映射为当前组件的计算属性
computed: {
...mapState(['count'])
}
}
3. mutations
Mutation 用于变更 Store中 的数据。
- 只能通过 mutation 变更 Store 数据,不可以直接操作 Store 中的数据。
- mutation 必须是同步函数,这样才能立刻捕捉 state 改变前后的快照
3.1 定义 mutations
mutations: {
// 修改数据的函数
add (state) {
state.count++
},
// 带参数修改数据的函数,num 是传入的参数
addn (state, num) {
state.count += num
}
},
3.2 使用 mutations
1、原始形式,this.$store.commit
<template>
<div>state的数据:{{ this.$store.state.count }}</div>
<button @click="add">累加1</button>
<button @click="addn(10)">累加10</button>
</template>
<script>
export default {
methods: {
add () {
// 调用store中的mutations,commit('muations名称')
this.$store.commit('add')
},
addn () {
// 传入参数
this.$store.commit('add', 10)
}
}
}
</script>
2、辅助函数,mapMutations,和 mapState 使用很像
// 1. 从 vuex 中按需导入 mapMutations 函数
import { mapMutations } from 'vuex'
export default {
methods: {
// 2. 将指定的 mutations 函数,映射为当前组件的 methods 函数
...mapMutations(['add', 'addn'])
}
}
需要注意的是:mutations 中不能写异步代码,如果有异步的 ajax 请求,应该放置在 actions 中
4. actions
如果通过异步操作变更数据,必须通过 Action,而不能使用 Mutation,但是在 Action 中还是要通过触发
Mutation 的方式间接变更数据。
4.1 定义 actions
actions: {
// 获取异步的数据,context表示当前的store的实例
// 可以用context.state获取状态、context.commit提交mutations、context.diapatch调用其他的action
addAsync (context) {
setTimeout(() => {
// 一秒钟之后去修改state
context.commit('add')
}, 1000)
},
// 带参
addnAsync (context, payload) {
setTimeout(() => {
context.commit('addn', payload)
}, 1000)
}
}
4.2 使用 actions
1、原始调用,this.$store.dispatch
<template>
<div>state的数据:{{ this.$store.state.count }}</div>
<button @click="addAsync">异步累加1</button>
<button @click="addnAsync(20)">异步累加20</button>
</template>
<script>
export default {
methods: {
addAsync () {
this.$store.dispatch('addAsync')
},
addnAsync (payload) {
this.$store.dispatch('addnAsync', payload)
}
}
}
</script>
2、辅助函数,mapActions
// 1. 从 vuex 中按需导入 mapActions 函数
import { mapActions } from 'vuex'
export default {
// 2. 将指定的 actions 函数,映射为当前组件的 methods 函数
methods: {
...mapActions(['addAsync', 'addnAsync'])
}
}
5. getters
getter 用于对 store 中的数据进行加工处理形成新的数据。例如对列表进行过滤并计数
- store 中数据发生变化, getter 的数据也会跟着变化,可以把 getter 认为是 store 的计算属性
5.1 定义getters
const store = createStore({
state: {
count: 10
},
// getters函数的第一个参数是 state,必须要有返回值
getters: {
cubeCount (state) {
return Math.pow(state.count, 3)
}
}
})
5.2 使用getters
1、原始调用,通过this直接使用
<template>
<div>{{ $store.getters.cubeCount }}</div>
</template>
2、辅助函数,mapGetters
<template>
<div>{{ cubeCount }}</div>
</template>
<script>
// 1. 从 vuex 中按需导入 mapGetters 函数
import { mapGetters } from 'vuex'
export default {
// 2. 将全局数据,映射为当前组件的计算属性
computed: {
...mapGetters(['cubeCount'])
}
}
</script>
6. modules
当项目变得越来越大的时候,store 对象就会变得相当臃肿,此时就需要 Vuex 的模块化
6.1 定义modules
建议模块都带上命名控件,这样分割的更彻底,更好维护
// 导入
import { createStore } from 'vuex'
// A模块
const moduleA = {
// 开启命名空间:让你的 state mutations getters actions 完全分割
namespaced: true,
// 避免数据污染,模块中state建议写成函数
state () {
return { count: 100 }
},
mutations: {
add (state) {
state.count++
}
},
actions: {
addAsync (ctx) {
setTimeout(() => {
ctx.commit('add')
}, 1000)
}
},
getters: {
cubeCount (state) {
return Math.pow(state.count, 3)
}
}
}
// B模块
const moduleB = {
namespaced: true,
// 避免数据污染,模块中state建议写成函数
state () {
return { count: 1000 }
},
mutations: {
addn (state) {
state.count += 100
}
}
}
// 创建一个新的 store 实例
const store = createStore({
// modules 配置选项定义模块
modules: {
a: moduleA,
b: moduleB
}
})
// 默认导出
export default store
6.2 使用带命名空间的模块
使用时带上模块名称: 模块名称/函数名称
,使用辅助函数:(模块名称,[函数名称,...])
<template>
A组件 {{ a.count }} --- {{ cubeCount }}
<br />
B组件 {{ b.count }}
<br />
<button @click="add">A 累计1</button>
<button @click="addn(100)">B 累计100</button>
<button @click="addAsync">A 异步累加数据</button>
</template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
computed: {
// 'a' 是模块名称
...mapState({
a: (state) => state.a,
b: (state) => state.b
}),
...mapGetters('a', ['cubeCount'])
},
methods: {
// 定义了add函数调用,类似:this.$store.commit('a/add')
...mapMutations('a', ['add']),
...mapMutations('b', ['addn']),
// 定义了addAsync函数调用 this.$store.dispatch('a/addAsync')
...mapActions('a', ['addAsync'])
}
}
</script>
四、Vuex 的 todoList 案例
1. 案例效果
2. 实现步骤
- 搭建项目基本结构
- 动态加载任务列表数据,实现文本框与store数据的双向同步
- 完成添加、删除任务事项的操作
- 动态绑定复选框的选中状态
- 剩余项统计与清除完成事项
- 点击选项卡切换事项
3. 搭建项目基本结构
1、创建项目名,安装依赖
vue create vuex-todolist
npm install vuex axios ant-design-vue –S
2、然后打开 public 文件夹,创建一个 list.json 文件,文件代码如下:
[
{
"id": 0,
"info": "Racing car sprays burning fuel into crowd.",
"done": false
},
{
"id": 1,
"info": "Japanese princess to wed commoner.",
"done": false
},
{
"id": 2,
"info": "Australian walks 100km after outback crash.",
"done": false
},
{
"id": 3,
"info": "Man charged over missing wedding girl.",
"done": false
},
{
"id": 4,
"info": "Los Angeles battles huge wildfires.",
"done": false
}
]
3、再接着,打开main.js,添加store.js的引入,如下:
import { createApp } from 'vue'
import App from './App.vue'
// 1. 导入 store
import store from './store'
// 2. 导入 ant-design-vue 组件库,样式表
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
const app = createApp(App)
// 3. store 和 Antd 安装
app.use(store).use(Antd)
app.mount('#app')
4. 动态加载任务列表数据
1、打开 store.js,添加 axios 请求 json 文件获取数据,代码如下:
import { createStore } from 'vuex'
import axios from 'axios'
export default createStore({
state: {
// 所有任务列表
list: [],
// 文本输入框中的值
inputValue: 'AAA',
// 下一个id
nextId: 5
},
mutations: {
initList (state, list) {
state.list = list
},
setInputValue (state, value) {
state.inputValue = value
}
},
actions: {
getList (context) {
axios.get('/list.json').then(({ data }) => {
context.commit('initList', data)
})
}
}
})
2、打开 App.vue 文件,将 store 中的数据获取并展示:
<template>
<div id="app">
<a-input
placeholder="请输入任务"
class="my_ipt"
:value="inputValue"
@change="handleInputChange"
/>
<a-button type="primary">添加事项</a-button>
<a-list bordered :dataSource="list" class="dt_list">
<template #renderItem="{ item }">
<a-list-item>
<!-- 复选框 -->
<a-checkbox :checked="item.done">{{ item.info }}</a-checkbox>
<!-- 删除链接 -->
<a>删除</a>
</a-list-item>
</template>
<!-- footer区域 -->
<div class="footer">
<!-- 未完成的任务个数 -->
<span>0条剩余</span>
<!-- 操作按钮 -->
<a-button-group>
<a-button type="primary">全部</a-button>
<a-button>未完成</a-button>
<a-button>已完成</a-button>
</a-button-group>
<!-- 把已经完成的任务清空 -->
<a>清除已完成</a>
</div>
</a-list>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {}
},
created () {
this.$store.dispatch('getList')
},
methods: {
handleInputChange (e) {
this.$store.commit('setInputValue', e.target.value)
}
},
computed: {
...mapState(['list', 'inputValue'])
}
}
</script>
<style scoped>
#app {
padding: 10px;
}
.my_ipt {
width: 500px;
margin-right: 10px;
}
.dt_list {
width: 500px;
margin-top: 10px;
}
.footer {
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
5. 添加和删除任务事项
1、打开App.vue文件,给"添加事项"按钮、"删除"超链接,分别绑定点击事件
<!-- 绑定事件 -->
<a-button type="primary" @click="addItemToList">添加事项</a-button>
<!-- 删除链接 -->
<a @click="removeItemById(item.id)">删除</a>
2、在 methods 下编写处理函数:
addItemToList () {
// 向列表中新增事项
if (this.inputValue.trim().length <= 0) {
return this.$message.warning('文本框内容不能为空')
}
this.$store.commit('addItem')
},
removeItemById (id) {
// 根据id删除事项
this.$store.commit('removeItem', id)
}
3、打开 store.js 文件,在 mutations 下编写 addItem 和 removeItem:
// 添加列表项
addItem (state) {
const obj = {
id: state.nextId,
info: state.inputValue.trim(),
done: false
}
// 将创建好的事项添加到数组list中
state.list.push(obj)
// 将nextId值自增
state.nextId++
state.inputValue = ''
},
// 根据id删除事项数据
removeItem (state, id) {
const index = state.list.findIndex((x) => x.id === id)
if (index !== -1) state.list.splice(index, 1)
}
6. 动态绑定复选框的选中状态
1、打开App.vue文件,给“复选”按钮绑定点击事件,编写处理函数
<!-- 复选框 -->
<a-checkbox
:checked="item.done"
@change="cbStateChanged(item.id, $event)"
>{{ item.info }}</a-checkbox
2、在 methods 下编写函数
// 复选框状态改变时触发
cbStateChanged (id, e) {
const param = {
id: id,
status: e.target.checked
}
// 根据id更改事项状态
this.$store.commit('changeStatus', param)
}
3、打开 store.js 在 mutations 下编写 changeStatus
changeStatus (state, param) {
// 根据id改变对应事项的状态
const index = state.list.findIndex((x) => x.id === param.id)
if (index !== -1) state.list[index].done = param.status
}
7. 剩余项统计与清除完成事项
1、打开store.js,在 mutations 下添加 cleanDone 清除方法,添加 getters 完成剩余项统计
cleanDone (state) {
state.list = state.list.filter((x) => x.done === false)
}
// ... 省略中间代码
getters: {
unDoneLength (state) {
const temp = state.list.filter((x) => x.done === false)
return temp.length
}
}
2、打开 App.vue,使用 getters 展示剩余项,并给“清除已完成”按钮绑定点击事件
<!-- 未完成的任务个数 -->
<span>{{ unDoneLength }}条剩余</span>
... 省略中间代码
<!-- 把已经完成的任务清空 -->
<a @click="clean">清除已完成</a>
3、在 methods 下编写 clean 函数,并添加 getters 的映射
// 导入getters
import { mapState,mapGetters } from 'vuex'
// 映射
computed: {
...mapState(['list', 'inputValue']),
...mapGetters(['unDoneLength'])
}
// 编写事件处理函数
clean () {
// 清除已经完成的事项
this.$store.commit('cleanDone')
}
8. 点击选项卡切换事项
1、打开App.vue,给全部,未完成,已完成三个选项卡绑定点击事件,修改 a-list 数据源为 infoList
<a-list bordered :dataSource="infoList" class="dt_list">
// ...省略其他代码
<!-- 操作按钮 -->
<a-button-group>
<a-button
:type="viewKey === 'all' ? 'primary' : 'default'"
@click="changeList('all')"
>全部</a-button>
<a-button
:type="viewKey === 'undone' ? 'primary' : 'default'"
@click="changeList('undone')"
>未完成</a-button>
<a-button
:type="viewKey === 'done' ? 'primary' : 'default'"
@click="changeList('done')"
>已完成</a-button>
</a-button-group>
2、编写处理函数,并将列表数据来源更改为一个 getters。
// 编写事件处理函数以及映射计算属性
methods:{
// ...省略其他代码
changeList( key ){
// 点击“全部”,“已完成”,“未完成”时触发
this.$store.commit('changeKey',key)
}
},
computed:{
...mapState(['list','inputValue','viewKey']),
...mapGetters(['unDoneLength','infoList'])
}
3、打开 store.js,添加 getters,mutations,state
state: {
// ...省略其他代码
// 保存默认的选项卡值
viewKey: 'all'
},
mutations: {
// ...省略其他代码
changeKey (state, key) {
// 当用户点击“全部”,“已完成”,“未完成”选项卡时触发
state.viewKey = key
}
},
// ...省略其他代码
getters: {
// ...省略其他代码
infoList (state) {
if (state.viewKey === 'all') {
return state.list
}
if (state.viewKey === 'undone') {
return state.list.filter((x) => x.done === false)
}
if (state.viewKey === 'done') {
return state.list.filter((x) => x.done === true)
}
}
}