官方文档
安装
安装vuex
npm install vuex@next --save
yarn add vuex@next --save
安装Promise(Vuex 依赖 Promise)
npm install es6-promise --save # npm
yarn add es6-promise # Yarn
核心概念
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
store.js
import { createStore } from 'vuex'
//创建一个新的store实例
const store = createStore({
//存储数据
state() {
return {
count: 0
}
},
//修改数据的方法
mutations: {
increment(state) {
state.count++
}
}
})
export default store;
main.js
import store from './store/store'
const app = createApp(App)
//将store实例作为插件安装
app.use(store)
你可以通过 store.state 来获取状态对象,并通过 store.commit 方法触发状态变更:
<template>
<div>
<el-button type="primary" @click="increment">修改状态</el-button>
<div>状态值: {{ count }}</div>
</div>
</template>
<script>
import { useStore } from "vuex";
import { ref } from "vue";
export default {
setup() {
//获取store实例
let store = useStore();
let count = ref(0);
let increment = () => {
//修改状态
store.commit("increment");
//获取状态
count.value = store.state.count;
};
return {
increment,
count,
};
},
};
</script>
<style scoped lang="scss">
</style>
在这里犯了个错误,就是导入useStore
和ref
这两个不是来自一个包
State
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。
- 获取状态
//方式1:通过ref返回一个响应式数据,就是上面那个例子
//方式2:通过计算属性返回一个响应式数据
console.log(computed(() => store.state.count).value);
//方式3:mapState 辅助函数,这里不介绍
Getter
有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数。
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。
实例: 根据用户id获取用户名
- store.js
import { createStore } from 'vuex'
//创建一个新的store实例
const store = createStore({
//存储数据
state() {
return {
userList: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]
}
},
getters: {
//根据用户id获取用户名
getNameById: (state) => (id) => {
let user = state.userList.find(e => e.id == id)
if (user) {
return user.name
}
},
//返回用户列表
getUserList: (state) => {
return state.userList
}
}
})
export default store;
- vuexTest.vue
<template>
<div>
<el-button type="primary" @click="getUserInfo">获取用户</el-button>
<div>id为1的用户是: {{ userName }}</div>
</div>
</template>
<script>
import { useStore } from "vuex";
import { ref } from "vue";
export default {
setup() {
//获取store实例
let store = useStore();
let userName = ref("");
let getUserInfo = () => {
userName.value = store.getters.getNameById(1);
console.log("用户列表:", store.getters.getUserList);
};
return {
getUserInfo,
userName,
};
},
};
</script>
<style scoped lang="scss">
</style>
- mapGetters 辅助函数(略)
Mutation
- 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const store = createStore({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
- 你不能直接调用一个 mutation 处理函数。你需要以相应的 type 调用 store.commit 方法:
store.commit('increment')
- 提交载荷(Payload)
//可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload)
//在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
- Mutation 必须是同步函数
Action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
const store = createStore({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
- 分发 Action、在组件中分发 Action、组合 Action(略)
Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
- 模块的局部状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState:
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
- 命名空间、模块动态注册(略)
进阶
项目结构
Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
- 大型应用,最好把vuex相关代码分割到模块中
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
其他内容略