Vuex 中 state、mutations 和 actions 的原理和写法

详细解释一下 Vuex 中 statemutationsactions 的原理和写法。

结论先行:

  • state 的写法是固定的:它必须是一个返回对象的函数(在模块中),这是 Vuex 的硬性要求。
  • mutationsactions 的"写法"不是语法上的固定不变,而是一种强烈推荐的"设计范式"和"最佳实践"。你可以不这么写,但代价是失去 Vuex 的核心优势。

下面我们来逐一拆解它们的原理和作用。


1. state - 状态容器

是什么?
state 是 Vuex 中存储应用状态(数据)的地方。它就像一个全局的 data(),但它不属于任何一个组件,而是属于整个应用。

原理:

  • 单一数据源 :Vuex 鼓励使用一个单一的 state 树来管理应用的所有状态。这使得应用的状态变化一目了然。
  • 响应式state 中的数据是响应式的。当 state 发生变化时,所有依赖于它的组件都会自动重新渲染。这和 Vue 组件内的 data 原理完全一样。
  • 如何在组件中使用 :通过 this.$store.state 或辅助函数 mapState 来访问。

为什么在模块中必须是函数?

javascript 复制代码
// store/modules/popup.js
const state = () => ({
  // ...
});

这是为了防止状态污染 。如果 state 是一个普通对象,那么当这个模块被多次引用时(虽然在 Vuex 中模块是单例的,但这是一个好的设计原则),它们会共享同一个对象引用。使用函数可以确保每次都返回一个全新的状态对象实例。

你的 popup.js 中的 state

javascript 复制代码
const state = {
  popupState: {
    show: false,
    title: '远方好物准用户',
    // ... 其他属性
  }
};

这里定义了一个名为 popupState 的状态,它包含了控制弹窗显隐、标题、内容等所有数据。


2. mutations - 状态修改器

是什么?
mutations唯一 允许你修改 state 的地方。它是一个包含了一系列"事件"的对象,每个事件都有一个字符串类型的事件类型 (type) 和一个回调函数 (handler)

原理:

  • 同步执行mutations 中的函数必须是同步的。这是一个非常重要的原则。为什么?因为 Vuex 提供了"时间旅行"(Time Travel)调试功能,可以记录每一次 mutation 的执行,并重放它们。如果 mutation 中有异步操作,就无法准确追踪状态的变化。
  • 如何触发 :不能直接调用 mutation 函数,必须通过 store.commit('mutation类型', 载荷) 来触发。
  • 载荷(Payload)commit 可以传递一个额外的参数,称为 payload,它可以是任何类型的数据,用于给 mutation 提供修改状态所需的信息。

为什么必须通过 mutations 修改 state

想象一下,如果任何组件都可以在任何时候、以任何方式直接修改 $store.state,那么当应用变得复杂时,你将完全无法追踪状态是如何、何时、被谁改变的。这会让调试变得极其困难。mutations 提供了一个集中式的、可追踪的、同步的状态修改机制,让状态变化变得清晰和可预测。

你的 popup.js 中的 mutations

javascript 复制代码
const mutations = {
  updatePopupState(state, payload) {
    state.popupState = { ...state.popupState, ...payload };
  },
  resetPopupState(state) {
    // ... 重置逻辑
  }
};
  • updatePopupState:这是一个 mutation。当你在组件中调用 this.$store.commit('popup/updatePopupState', { show: true }) 时,Vuex 会找到这个 mutation 并执行它。
  • state 参数:Vuex 会自动将 state 对象作为第一个参数传入。
  • payload 参数:这里的 payload 是一个对象 { show: true }mutation 函数将 payload 的内容合并到 state.popupState 中,从而实现了状态的更新。使用展开运算符 ... 是为了创建一个新对象,而不是直接修改原对象,这有助于 Vue 的响应式系统检测到变化。

3. actions - 异步操作处理器

是什么?
actions 用于处理异步操作 (例如:API 请求、定时器等)。它类似于 mutations,但它不能直接修改 state,而是通过调用 mutations 来间接修改 state

原理:

  • 异步执行actions 中的函数可以是异步的(使用 async/await 或返回 Promise)。
  • 如何触发 :通过 store.dispatch('action类型', 载荷) 来触发。
  • 提交 mutationsaction 函数接收一个与 store 实例具有相同方法和属性的 context 对象。你可以通过 context.commit() 来调用 mutation
  • 可以返回结果action 可以返回一个 Promise,让调用者知道异步操作何时完成。

为什么需要 actions

因为 mutations 必须是同步的,所以所有的异步逻辑都应该放在 actions 中。action 负责完成异步操作,获取到结果后,再通过 commit 去调用 mutation 来更新 state。这样就保证了 mutation 的纯净性和同步性,同时也让异步流程变得清晰。

你的 popup.js 中的 actions

javascript 复制代码
const actions = {
  openPopup({ commit }, options) {
    commit('updatePopupState', { ...options, show: true });
  },
  closePopup({ commit }) {
    commit('updatePopupState', { show: false });
    setTimeout(() => {
      commit('resetPopupState');
    }, 300);
  }
};
  • openPopup:这是一个 action。当你在组件中调用 this.$store.dispatch('popup/openPopup', { title: '新标题' }) 时,这个 action 会被触发。
  • context 参数:这里使用了解构赋值 { commit },直接从 context 对象中获取了 commit 方法。
  • 逻辑:action 接收到 options(例如 { title: '新标题' }),然后将其与 { show: true } 合并,作为 payload 调用 updatePopupState mutation。这确保了打开弹窗的同时,可以更新其他相关状态。
  • closePopup:这个 action 展示了异步逻辑。它首先 commit 一个 mutationshow 设置为 false(弹窗开始关闭动画)。然后,它通过 setTimeout 等待 300 毫秒(动画时长),之后再 commit 一个 mutation 来重置弹窗的所有状态。这个流程确保了用户体验的平滑性。

总结:核心工作流程

以你的弹窗为例,整个流程是这样的:

  1. 组件触发 Action :用户点击按钮,组件中调用 this.$store.dispatch('popup/openPopup', { title: '提示' })
  2. Action 处理逻辑openPopup action 接收到参数,并立即调用 commit 触发一个 mutation
  3. Mutation 修改 StateupdatePopupState mutation 被触发,它接收 payload 并更新 state.popupState
  4. State 变化驱动视图更新 :由于 state 是响应式的,所有依赖 $store.state.popupState 的组件(你的 g-custom-popup 组件)都会检测到变化并重新渲染,从而显示出弹窗。

statemutationsactions 的分工合作,构成了 Vuex 清晰、可维护、可调试的核心架构。这不是一套死板的语法,而是一套经过实践检验的、优秀的状态管理设计模式。

相关推荐
y***86692 小时前
前端CSS-in-JS方案
前端·javascript·css
暖木生晖2 小时前
APIs之WEB API的基本认知是什么?
前端·dom·dom树·web apis
华仔啊2 小时前
你真的懂递归吗?没那么复杂,但也没那么简单
前端·javascript
火星数据-Tina2 小时前
低成本搭建体育数据中台:一套 API 如何同时支撑比分网与 App?
java·前端·websocket
yddddddy2 小时前
深入浅出前端路由:从概念到实战
前端
专业抄代码选手2 小时前
【Leetcode】1930. 长度为 3 的不同回文子序列
javascript·算法·面试
林_xi3 小时前
uniapp使用@uni-ku/root插件实现全局组件
前端·uni-app
计算机毕设定制辅导-无忧学长3 小时前
基于uni-app的“民族风韵”特色购物小程序
uni-app
一个处女座的程序猿O(∩_∩)O3 小时前
UniApp 生命周期全解析:从应用到页面,再到组件的完美协奏曲
前端·uni-app