Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
按照自己理解来说就是公共数据管理模块。如果应用中数据量比较大的可以应用,不是很大的建议用缓存即可。
使用准备在项目中新建store目录,在该目录下新建index.js文件
由于uniapp中内置了vuex,只需要规范引入即可:
// 页面路径:store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex);//vue的插件机制 //Vuex.Store 构造器选项 const store = new Vuex.Store({ state:{//存放状态 "username":"foo", "age":18 } }) export default store
// 页面路径:main.js import Vue from 'vue' import App from './App' import store from './store' Vue.prototype.$store = store App.mpType = 'app' // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件 const app = new Vue({ store, ...App }) app.$mount()
第一种方法:通过属性访问,需要在根节点注入 store 。
<!-- 页面路径:pages/index/index.vue --> <template> <view> <text>用户名:{{username}}</text> </view> </template> <script> import store from '@/store/index.js';//需要引入store export default { data() { return {} }, computed: { username() { return store.state.username } } } </script>
第二种方法:在组件中使用,通过 this.$store 访问到 state 里的数据。
<!-- 页面路径:pages/index/index.vue --> <template> <view> <text>用户名:{{username}}</text> </view> </template> <script> export default { data() { return {} }, computed: { username() { return this.$store.state.username } } } </script>
进阶方法:通过 mapState 辅助函数获取。当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。 为了解决这个问题,我们可以使用 mapState 辅助函数 帮助我们生成计算属性,让你少按几次键,(说白了就是简写,不用一个一个去声明了,避免臃肿)
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view>用户名:{{username}}</view> <view>年龄:{{age}}</view> </view> </template> <script> import { mapState } from 'vuex'//引入mapState export default { data() { return {} }, computed: mapState({ // 从state中拿到数据 箭头函数可使代码更简练(就是简写) username: state => state.username, age: state => state.age, }) } </script>
可以认为是 store 的计算属性,对 state 的加工,是派生出来的数据。 就像 computed 计算属性一样,getter 返回的值会根据它的依赖被缓存起来,且只有当它的依赖值发生改变才会被重新计算。(重点,响应式数据的应用)
可以在多组件中共享 getter 函数,这样做还可以提高运行效率。
// 页面路径:store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); const store = new Vuex.Store({ state: { todos: [{ id: 1, text: '我是内容一', done: true }, { id: 2, text: '我是内容二', done: false } ] }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } } }) export default store
Getter属性接收传递参数,主要为:state, 如果在模块中定义则为模块的局部状态。getters, 等同于 store.getters。
// 页面路径:store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); const store = new Vuex.Store({ 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
应用Getter属性方法一:通过属性访问,Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值。
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view v-for="(item,index) in todos"> <view>{{item.id}}</view> <view>{{item.text}}</view> <view>{{item.done}}</view> </view> </view> </template> <script> import store from '@/store/index.js'//需要引入store export default { computed: { todos() { return store.getters.doneTodos } } } </script>
应用Getter属性方法二:通过 this.$store 访问。
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view v-for="(item,index) in todos"> <view>{{item.id}}</view> <view>{{item.text}}</view> <view>{{item.done}}</view> </view> </view> </template> <script> export default { computed: { todos() { return this.$store.getters.doneTodos } } } </script>
应用Getter属性方法三:你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。(一次调用,一次请求,没有依赖关系)
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view v-for="(item,index) in todos"> <view>{{item}}</view> </view> </view> </template> <script> export default { computed: { todos() { return this.$store.getters.getTodoById(2) } } } </script>
应用Getter属性方法进阶(简写):通过 mapGetters 辅助函数访问。
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view>{{doneTodosCount}}</view> </view> </template> <script> import {mapGetters} from 'vuex' //引入mapGetters export default { computed: { // 使用对象展开运算符将 getter 混入 computed 对象中 ...mapGetters([ 'doneTodos', 'doneTodosCount', // ... ]) } } </script>
通俗的理解,mutations 里面装着改变数据的方法集合,处理数据逻辑的方法全部放在 mutations 里,使数据和视图分离。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
// 页面路径:store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); const store = new Vuex.Store({ state: { count: 1 }, mutations: { add(state) { // 变更状态 state.count += 2 } } }) export default store
你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 add 的 mutation 时,调用此函数”,要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法。
注意:store.commit 调用 mutation(需要在根节点注入 store)。
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view>数量:{{count}}</view> <button @click="addCount">增加</button> </view> </template> <script> import store from '@/store/index.js' export default { computed: { count() { return this.$store.state.count } }, methods: { addCount() { store.commit('add') } } } </script>
mutation中的函数接收参数:你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload):
// 页面路径:store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); const store = new Vuex.Store({ state: { count: 1 }, mutations: { //传递数值 add(state, n) { state.count += n } //传递对象类型 add(state, payload) { state.count += payload.amount } } }) export default store
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view>数量:{{count }}</view> <button @click="addCount">增加</button> </view> </template> <script> import store from '@/store/index.js' export default { computed: { count() { return this.$store.state.count } }, methods: { //传递数值 addCount() { store.commit('add', 5)//每次累加 5 } //传递对象 addCount () {//把载荷和type分开提交 store.commit('add', { amount: 10 }) } } } </script>
应用Getter属性方法二:通过 this.$store 访问。
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view v-for="(item,index) in todos"> <view>{{item.id}}</view> <view>{{item.text}}</view> <view>{{item.done}}</view> </view> </view> </template> <script> export default { computed: { todos() { return this.$store.getters.doneTodos } } } </script>
应用mutation属性方法进阶(简写):通过 mapMutations辅助函数访问。
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view>数量:{{count}}</view> <button @click="add">增加</button> </view> </template> <script> import { mapMutations } from 'vuex'//引入mapMutations export default { computed: { count() { return this.$store.state.count } }, methods: { ...mapMutations(['add'])//对象展开运算符直接拿到add } } </script>
action 提交的是 mutation,通过 mutation 来改变 state,而不是直接变更状态,action 可以包含任意异步操作。
// 页面路径:store/index.js // 页面路径:store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); const store = new Vuex.Store({ state: { count: 1 }, mutations:{ add(state) { // 变更状态 state.count += 2 } }, actions:{ addCountAction (context) { context.commit('add') } } }) export default store
action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
actions 通过 store.dispatch 方法触发:
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view>数量:{{count}}</view> <button @click="add">增加</button> </view> </template> <script> import store from '@/store/index.js'; export default { computed: { count() { return this.$store.state.count } }, methods: { add () { store.dispatch('addCountAction') } } } </script>
actions 支持以载荷形式分发:
// 页面路径:store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); const store = new Vuex.Store({ state: { count: 1 }, mutations:{ add(state, payload) { state.count += payload.amount } }, actions:{ addCountAction (context , payload) { context.commit('add',payload) } } }) export default store
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view>数量:{{count }}</view> <button @click="add">增加</button> </view> </template> <script> import store from '@/store/index.js'; export default { computed: { count() { return this.$store.state.count } }, methods: { add () { // 以载荷形式分发 store.dispatch('addCountAction', {amount: 10}) } add () { // 以对象形式分发 store.dispatch({ type: 'addCountAction', amount: 5 }) } } } </script>
actions的异步:
// 页面路径:store/index.js //调用mutations的方法来改变数据(同步、异步) actions:{ //异步调用 actionA({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('add',5) resolve() }, 2000) }) } actionB ({ dispatch, commit }) { return dispatch('actionA').then(() => {commit('someOtherMutation') }) async actionA ({ commit }) {commit('gotData', await getData()) }, async actionB ({ dispatch, commit }) { await dispatch('actionA') // 等待 actionA 完成 commit('gotOtherData', await getOtherData()) } } }
应用Actions属性方法进阶(简写):通过 mapActions辅助函数访问。
<!-- 页面路径:pages/index/index.vue --> <template> <view> <view>数量:{{count }}</view> <button @click="addCountAction">增加</button> </view> </template> <script> import { mapActions } from 'vuex' export default { computed: { count() { return this.$store.state.count } }, methods: { ...mapActions([ 'addCountAction', // 将 `this.addCountAction()` 映射为 `this.$store.dispatch('addCountAction')` ]) } } </script>
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
1.在 store 文件夹下新建 modules 文件夹,并在下面新建 moduleA.js 和 moduleB.js 文件用来存放 vuex 的 modules 模块。
├── 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。
// 页面路径:main.js import Vue from 'vue' import App from './App' import store from './store' Vue.prototype.$store = store // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件 const app = new Vue({ store, ...App }) app.$mount()
3.在项目根目录下,新建 store 文件夹,并在下面新建 index.js 文件,作为模块入口,引入各子模块。
// 页面路径:store/index.js import Vue from 'vue' import Vuex from 'vuex' import moduleA from '@/store/modules/moduleA' import moduleB from '@/store/modules/moduleB' Vue.use(Vuex) export default new Vuex.Store({ modules:{ moduleA,moduleB } })
4.子模块 moduleA 页面内容。
// 子模块moduleA路径:store/modules/moduleA.js export default { state: { text:"我是moduleA模块下state.text的值" }, getters: { }, mutations: { }, actions: { } }
5.子模块 moduleB 页面内容。
// 子模块moduleB路径:store/modules/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 中的初始数据。
<!-- 页面路径:pages/index/index.vue --> <template> <view class="content"> <view>{{text}}</view> <view>时间戳:{{timestamp}}</view> <view>当前时间:{{timeString}}</view> <myButton></myButton> </view> </template> <script> import {mapState,mapGetters} from 'vuex' export default { computed: { ...mapState({ text: state => state.moduleA.text, timestamp: state => state.moduleB.timestamp }), ...mapGetters([ 'timeString' ]) } } </script>
7.在组件 myButton中,通过 mutations 操作刷新当前时间。
<!-- 组件路径:components/myButton/myButton.vue --> <template> <view> <button type="default" @click="updateTime">刷新当前时间</button> </view> </template> <script> import {mapMutations} from 'vuex' export default { data() { return {} }, methods: { ...mapMutations(['updateTime']) } } </script>
vue是单向数据流,子组件不能直接修改父组件的数据,而通过vuex状态管理实现:把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
vuex的整体结构并不复杂,知识规范比较繁琐,自己多试几遍就好了。