uni-app状态管理之vuex

一、介绍

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
uni-app 内置了 Vuex。

每一个 Vuex 应用的核心就是 store(仓库),它包含着你的应用中大部分的状态 (state)。
状态管理有5个核心:state、getter、mutation、action、module。

1、优势与使用场景

  • Vuex的状态存储是响应式的,可跟踪每一个状态变化,一旦它改变,所有关联组件都会自动更新相对应的数据。
  • 共享数据,解决了非父子组件的消息传递(将数据存放在state中)。
  • 统一状态管理,减少了请求次数,有些情景可以直接从内存中的state获取数据。

2、使用 Vuex 需要遵守的规则

  • 应用层级的状态应该集中到单个 store 对象中。

  • 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的

  • 异步逻辑都应该封装到 action 里面。

只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 actionmutationgetter 分割到单独的文件。

对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

├── 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

 

二、uni-app中配置vuex的步骤

1、创建vuex主脚本

uni-app 项目根目录下,新建 store 目录,在此目录下新建 index.js 文件。在 index.js 文件配置如下:

// 页面路径:store/index.js
import { createStore } from 'vuex'
const store = createStore({
    state:{ // 存放状态
        "username":"zzz",
        "age": 18
    }
})

export default store

如果需要对store进行二次计算(加工),Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性),对 state 的加工,是派生出来的数据。 可以在多组件中共享 getter 函数,这样做还可以提高运行效率。

store 上注册 gettergetter 方法接受以下参数:

  • state, 如果在模块中定义则为模块的局部状态
  • getters, 等同于 store.getters
import { createStore } from 'vuex'
const store = createStore({
    state: { // 存放状态
        "username": "zzz",
        "age": 18
    },
    getters: {
        myFriend(state) {
            return `${state.username}的朋友是小明`;
        },
        xiaomingAge(state, getters) {
            // state :可以访问数据
            // getters:访问其他函数,等同于 store.getters
            return `${getters.myFriend},他年龄是:${state.age}`
        }
    }
});

export default store

2、注册到app

main.js 中导入文件。

import App from './App'
import { createSSRApp } from 'vue'
import store from '@/store/index.js';
export
function createApp() { const app = createSSRApp(App) app.use(store) return { app } }

3、在组件中使用

<template>
    <view class="content">
        <text class="title">{{ name }}</text>
    </view>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import store from '@/store/index.js';

const name = computed(() => store.state.username) // vue2的话可以直接使用this.$store.state,或者 mapState
const myFriend = computed(() => store.getters.myFriend) // vue2的话可以直接使用this.$store.getters,或者 mapGetters
const xiaomingAge = computed(() => store.getters.xiaomingAge)
console.log('name: ', name.value)
console.log('myFriend: ', myFriend.value)
console.log('xiaomingAge: ', xiaomingAge.value)
</script>

<style>
    .content {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }
</style>

4、mutations修改store的状态

Vuex中store数据改变的唯一方法就是mutation。

通俗的理解,mutations 里面装着改变数据的方法集合,处理数据逻辑的方法全部放在 mutations 里,使数据和视图分离。

Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个事件类型 (type) 和 对应的回调函数 (handler)这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。

不能直接调用一个 mutation 回调函数 (handler)。要唤醒一个 mutation 回调函数 (handler),你需要以相应的 type 调用 store.commit 方法

  •  mutation 可以传递一个参数
// 页面路径:store/index.js
import { createStore } from 'vuex'
const store = createStore({
    state: { // 存放状态
        "username": "zzz",
        "age": 18
    },
    mutations: {
        changeName(state, newName) {
            state.username = newName;
        }
    }
});

export default store
// 页面路径:pages/index/index.vue
<template>
    <view class="content">
        <text>my name is: {{ name }}</text>
        <text @click="changeMyName">点击改名啦</text>
    </view>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import store from '@/store/index.js';

const name = computed(() => store.state.username)

function changeMyName() {
    store.commit('changeName', 'new name')
}

</script>

<style>
    .content {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }</style>

注意:Mutation 必须是同步函数,如果是类似下面这样异步的话:

mutations: {
    someMutation (state) {
        api.callAsyncMethod(() => {
            state.count++
        })
    }
}

我们就不知道什么时候状态会发生改变,所以也就无法追踪了,这与 mutation 的设计初心相悖,所以强制规定它必须是同步函数。

一条重要的原则就是要记住** mutation 必须是同步函数**,如果有异步逻辑需要处理,则可以使用action。

5、action异步间接更改状态

action 类似于 mutation ,不同在于:

  • action 提交的是 mutation,通过 mutation 来改变 state ,而不是直接变更状态
  • action 可以包含任意异步操作。

action 函数接受一个store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 stategetters

actions 通过 store.dispatch 方法触发。

// store/index.js
import { createStore } from 'vuex'
const store = createStore({
    state: {
        "age": 18
    },
    mutations: {
        setAge(state, newAge) {
            state.age = newAge;
        }
    },
    actions: {
        setAgeAction({ commit }, newAge) {
            // 模拟异步
             setTimeout(() => {
                commit('setAge', newAge)
             }, 1000)
        }
    }
});

export default store
// page/index/index.vue
<template>
    <view class="content">
        <text>我的年龄: {{ age }}</text>
        <button @click="changeAge">更改年龄</button>
    </view>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import store from '@/store/index.js';


const age = computed(() => store.state.age)

function changeAge() {
    store.dispatch('setAgeAction', 16)
}


</script>

<style>
    .content {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }
</style>

一般来说,异步的作用在于处理接口是否调用成功,例如有这样的一个场景:

我们在某APP进行购物,把购物车的商品放在state进行全局管理,如果你勾选了购物车里面的某个商品进行购买支付,当年点击给钱的时候,会去调用支付接口,等接口支付成功后才会把已经支付的这个商品移除出购物车,那么这个时候就必须要使用action进行异步处理,大概逻辑如下:

import { createStore } from 'vuex'
const store = createStore({
    state: {
        cart: {
            added: [] // 已经添加到购物车的商品id
        }
    },
    mutations: {
        checkoutSuccess(state, productIds) {
            state.cart.added = state.cart.added.filter(item => !productIds.includes(item));
        },
        checkoutFailed(state, productIds) {
            state.cart.added = productIds;
        }
    },
    actions: {
        pay ({ commit, state }, productIds) {
            // productIds: 已经勾选需要支付的商品id
            const savedCartItems = [...state.cart.added] // 备份当前购物车
            // 购物 API 接受一个成功回调和一个失败回调
            api.pay(productIds).then(res => {
                if (res.resultCode === '200') {
                  // 成功则把已支付的商品清理掉
                  commit('checkoutSuccess', productIds)
                }
              }).catch(() => {
                  // 失败则回退
                  commit('checkoutFailed', savedCartItems)
              });
        }
    }
});

export default store

 

6、Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 statemutationactiongetter、甚至是嵌套子模块——从上至下进行同样方式的分割:

1、在 store 文件夹下新建 modules 文件夹,并在下面新建 moduleA.jsmoduleB.js 文件用来存放 vuexmodules 模块。

├── 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 {createSSRApp} from 'vue'
import store from './store'
export function createApp() {
    const app = createSSRApp(App)
    app.use(store)
    return {
        app
    }
}

3、在项目根目录下,新建 store 文件夹,并在下面新建 index.js 文件,作为模块入口,引入各子模块。

// 页面路径:store/index.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 页面内容。

// 子模块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>

 

posted @ 2025-04-04 21:55  我用python写Bug  阅读(172)  评论(0)    收藏  举报