> 这是与 Vue 3 匹配的 Vuex 4 的文档。差异对比可参阅[从 3.x 迁移到 4.0](https://next.vuex.vuejs.org/zh/guide/migrating-to-4-0-from-3-x.html)
>
> 已经有 Vue2项目,需要适配 Vue3 的可参阅 [vue2 项目迁移 vue3](https://uniapp.dcloud.io/migration-to-vue3)!
uni-app 内置了 [Vuex](https://vuex.vuejs.org/zh/) 。
## 优势与使用场景
- Vuex的状态存储是响应式的,可跟踪每一个状态变化,一旦它改变,所有关联组件都会自动更新相对应的数据。
- 共享数据,解决了非父子组件的消息传递(将数据存放在state中)。
- 统一状态管理,减少了请求次数,有些情景可以直接从内存中的state获取数据。
### Vuex与全局变量区别
|vuex |全局变量|
|-- |-- |
|不能直接改变store里面的变量,由统一的方法修改数据 |可以任意修改 |
|每个组件可以根据自己vuex的变量名引用不受影响 |全局变量可能操作命名污染 |
|解决了多组件之间通信的问题 |跨页面数据共享 |
|适用于多模块、业务关系复杂的中大型项目 |适用于demo或者小型项目 |
### 什么时候需要用vuex?
- 当一个组件需要多次派发事件时。例如购物车数量加减。
- 跨组件共享数据、跨页面共享数据。例如订单状态更新。
- 需要持久化的数据。例如登录后用户的信息。
- 当您需要开发中大型应用,适合复杂的多模块多页面的数据交互,考虑如何更好地在组件外部管理状态时。
## 项目结构
使用 Vuex 需要遵守的规则:
- 应用层级的状态应该集中到单个 `store` 对象中。
- 提交 `mutation` 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 `action` 里面。
只要你遵守以上规则,如何组织代码随你便。如果你的 `store` 文件太大,只需将 `action` 、`mutation` 和 `getter` 分割到单独的文件。
对于大型应用,我们会希望把 `Vuex` 相关代码分割到模块中。下面是项目结构示例:
```html
├── pages
├── static
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules # 模块文件夹
├── cart.js # 购物车模块
└── products.js # 产品模块
├── App.vue
├── main.js
├── manifest.json
├── pages.json
└── uni.scss
```
单一状态树,定义应用状态的默认初始值,页面显示所需的数据从该对象中进行读取。
1.在 `uni-app` 项目根目录下,新建 `store` 目录,在此目录下新建 `index.js` 文件。在 `index.js` 文件配置如下:
```js
import { createStore } from 'vuex'
const store = createStore({
state:{//存放状态
"username":"foo",
"age":18
}
})
export default store
```
2.在 `main.js` 中导入文件。
**获取state**
1.通过属性访问,需要在根节点注入 `store` 。
```html
用户名:{{username}}
```
2.在组件中使用,通过 `this.$store` 访问到 `state` 里的数据。
```html
用户名:{{username}}
```
#### mapState
3.通过 `mapState` 辅助函数获取。
### Getter
`Vuex` 允许我们在 `store` 中定义`“getter”`(可以认为是 `store` 的计算属性),对 `state` 的加工,是派生出来的数据。
可以在多组件中共享 `getter` 函数,这样做还可以提高运行效率。
> 从 Vue 3.0 开始,getter 的结果不再像计算属性一样会被缓存起来。[详见](https://next.vuex.vuejs.org/zh/guide/getters.html)
在 `uni-app` 项目根目录下,`store` 目录 `index.js` 文件下:
在 `store` 上注册 `getter`,`getter` 方法接受以下参数:
- state, 如果在模块中定义则为模块的局部状态
- getters, 等同于 store.getters
```js
import { createStore } from 'vuex'
const store = createStore({
state: {
todos: [{
id: 1,
text: '我是内容一',
done: true
},
{
id: 2,
text: '我是内容二',
done: false
}
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
doneTodosCount: (state, getters) => {
//state :可以访问数据
//getters:访问其他函数,等同于 store.getters
return getters.doneTodos.length
},
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
})
export default store
```
**获取getters**
2.通过 `this.$store` 访问。
4.通过 `mapGetters` 辅助函数访问。
通俗的理解,`mutations` 里面装着改变数据的方法集合,处理数据逻辑的方法全部放在 `mutations` 里,使数据和视图分离。
**注意**:`store.commit` 调用 `mutation`(需要在根节点注入 store)。
**传入参数**
还是以累加器的例子来实现 `mutation` 函数的传参,来动态定义累加的数量。
- 在 `mutation` 传参(载荷)可以传递一个参数。
- 在 `mutation` 传参(载荷)可以也可以传递一个对象。让我们修改上面累加器的例子:
`
```html
数量:{{count }}
```
**提交方式**
```html
数量:{{count }}
```
2.通过 `mapMutations` 辅助函数提交。
创建组件方法提交 `mutation`。
使用 `mapMutations` 辅助函数将组件中的 `methods` 映射为 `store.commit` 调用(需要在根节点注入 `store`)。
```html
数量:{{count}}
```
我们要通过提交 `mutation` 的方式来改变状态数据,是因为我们想要更明确地追踪到状态的变化。如果是类似下面这样异步的话:
我们就不知道什么时候状态会发生改变,所以也就无法追踪了,这与 `mutation` 的设计初心相悖,所以强制规定它必须是同步函数。
2.通过 `mapActions` 辅助函数分发。
- `mapActions` 也支持传入参数(载荷):
- `mapActions` 也支持传递一个对象:
1.在 `store` 文件夹下新建 `modules` 文件夹,并在下面新建 `moduleA.js` 和 `moduleB.js` 文件用来存放 `vuex` 的 `modules` 模块。
```html
├── components # 组件文件夹
└── myButton
└── myButton.vue # myButton组件
├── pages
└── index
└── index.vue # index页面
├── static
├── store
├── index.js # 我们组装模块并导出 store 的地方
└── modules # 模块文件夹
├── moduleA.js # 模块moduleA
└── moduleB.js # 模块moduleB
├── App.vue
├── main.js
├── manifest.json
├── pages.json
└── uni.scss
```
2.在 `main.js` 文件中引入 `store`。
```js
import {createSSRApp} from 'vue'
import store from './store'
export function createApp() {
const app = createSSRApp(App)
app.use(store)
return {
app
}
}
```
3.在项目根目录下,新建 `store` 文件夹,并在下面新建 `index.js` 文件,作为模块入口,引入各子模块。
```js
import {createStore} from 'vuex'
import moduleA from '@/store/modules/moduleA'
import moduleB from '@/store/modules/moduleB'
export default createStore({
modules: {
moduleA,
moduleB
}
})
```
4.子模块 `moduleA` 页面内容。
```js
export default {
state: {
text:"我是moduleA模块下state.text的值"
},
getters: {
},
mutations: {
},
actions: {
}
}
```
5.子模块 `moduleB` 页面内容。
```js
export default {
state: {
timestamp: 1608820295//初始时间戳
},
getters: {
timeString(state) {//时间戳转换后的时间
var date = new Date(state.timestamp);
var year = date.getFullYear();
var mon = date.getMonth()+1;
var day = date.getDate();
var hours = date.getHours();
var minu = date.getMinutes();
var sec = date.getSeconds();
var trMon = mon<10 ? '0'+mon : mon
var trDay = day<10 ? '0'+day : day
return year+'-'+trMon+'-'+trDay+" "+hours+":"+minu+":"+sec;
}
},
mutations: {
updateTime(state){//更新当前时间戳
state.timestamp = Date.now()
}
},
actions: {
}
}
```
6.在页面中引用组件 myButton ,并通过 `mapState` 读取 `state` 中的初始数据。
```html
{{text}}
时间戳:{{timestamp}}
当前时间:{{timeString}}
```
7.在组件 `myButton`中,通过 `mutations` 操作刷新当前时间。
```html
```
vue是单向数据流,子组件不能直接修改父组件的数据,而通过vuex状态管理实现:把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!