> 这是与 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 ``` 2.在组件中使用,通过 `this.$store` 访问到 `state` 里的数据。 ```html ``` #### 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 ``` **提交方式** ```html ``` 2.通过 `mapMutations` 辅助函数提交。 创建组件方法提交 `mutation`。 使用 `mapMutations` 辅助函数将组件中的 `methods` 映射为 `store.commit` 调用(需要在根节点注入 `store`)。 ```html ``` 我们要通过提交 `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 ``` 7.在组件 `myButton`中,通过 `mutations` 操作刷新当前时间。 ```html ``` vue是单向数据流,子组件不能直接修改父组件的数据,而通过vuex状态管理实现:把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!