揭开状态管理的神秘面纱

2025-06-10

揭开状态管理的神秘面纱

状态管理是现代 JavaScript 前端开发中最复杂、最受争议的话题之一。但本质上,它并没有那么复杂。只是我们让它变得复杂了。在本文中,我将尝试为您揭开状态和状态管理的神秘面纱,并挑战您对它们的思维模式。

什么是状态和状态管理?

状态是一种奇怪的数据存储方式吗?它是来自数据库的数据吗?不是。状态只不过是一个存在于作用域内的 JavaScript 值。它可以是布尔值、字符串,但在大多数情况下是一个(复杂的)对象。但它仍然是一个值。在大多数情况下,它甚至是一个与对象处于同一级别的对象window。它已经成为 JavaScript 环境(即浏览器窗口)中的一个全局值。在我们的代码(例如 UI 组件)中,我们可以使用此值来确定要显示的内容或允许哪些交互。在复杂的应用程序中,我们可以拥有几种不同类型的状态。但请记住,它们只不过是值而已。

  • 本地:单个 UI 组件使用的状态。
  • 共享:许多 UI 组件共用的状态。它通常由父组件或包装组件管理。
  • 全局:一种特殊的共享状态,因为它处于最高级别,所有 UI 组件(甚至辅助函数)都可以访问。
  • Meta:也称为“关于状态的状态”。它告诉你一些关于
  • 路由:存储在应用程序当前 URL 中的状态(例如对象 ID 或分页信息)。
  • 远程:来自服务器的数据副本。获取请求的响应在此状态下以一对一副本的形式存储。它不应该与服务器发生任何偏离(除非应用乐观 UI)。

那么状态管理又如何呢?对很多人来说,状态管理就像一个黑匣子。Redux 内部到底是怎么回事?为什么它感觉这么复杂?我是这样看待它的:状态管理只不过是我们用来管理状态使用和修改的模式。它不是黑匣子魔法,只是一些模式而已。为什么不把所有可以对状态进行的修改都集中到一个地方呢?不如给这些修改起一个简单易懂的名字?在复杂的应用程序中,采用这类模式可以使我们的代码更易于维护。至少人们是这么说的(虽然确实如此)。在下面的部分中,我们将深入探讨不同类型的状态管理模式。

事件驱动模式

最著名的模式是 Flux 模式。它因 Redux 包而流行起来。它是事件驱动模式的一个很好的例子。让我们仔细看看它的流程。用户通过视图,通过 Action 创建器来触发一个 Action。这看起来可能令人望而生畏或过于复杂。但正如我之前所说,它仅此而已。它是一种将所有可能的状态变化组合在一起的方法,并允许我们在 UI 组件中使用具有易记名称的简单“操作”。

替代文本

这种模式使我们能够保持 UI 组件代码简洁明了。当遇到状态异常的问题时,我们就知道该去哪里查找。这就是它被称为状态管理的原因。

这种模式的核心概念是Reducer。Reducer是一些大型复杂的 switch 语句,用于保存所有状态突变逻辑。有时它们真的感觉像个黑匣子。但不要被误导。这个概念其实很简单。去掉 switch 语句的复杂性后,你会得到类似下面的代码片段。Reducer 是一个简单的函数,它获取一个状态并返回一个状态。不多不少。它使用额外的输入来改变状态,或者什么也不做。

function reducer(state, { action, payload }) {
  ...
  return newState;
}
Enter fullscreen mode Exit fullscreen mode

Redux 严重依赖 Reducer。设置时,将所有 Reducer 添加到 Redux Store 中。Redux 真正将服务端事件驱动模式融入核心。所有 Reducer 都可以对已调度的 Action 进行操作。然而,我并没有在类似生产环境中见过这种情况。

事件驱动的状态管理与状态机相关。状态机使我们能够清晰地定义状态的形状,以及何时允许哪些突变。下面是一个动画提示框的状态机示例。此提示框应在 X 秒后消失。Redux样式指南向您展示了如何将 Reducer 建模为状态机。如果您觉得这很复杂,可以通过在 switch 语句中添加 if 语句来进一步理解。例如,“如果我们处于 Y 状态,则可以执行 X 操作”。

替代文本

原子模式

许多状态管理库强制你创建一个位于应用程序最高层的大型状态。这发生在我们将“远程”状态放入这个 store 的时代。但现在,像React QuerySWRApollo Client这样的解决方案已经为我们处理了这个问题。需要在全局层面管理的数据越来越少。将 store 设置注入到最高层组件包装器中也变得多余。

使用原子模式,我们可以拥有许多不同的全局状态,每个状态都由单个值组成。这种方法真正体现了 JavaScript 的本质,以及“状态就是值”的理念。每个原子都是一个值。在大多数情况下,原子也存在于 JavaScript 环境中的全局级别。但是,您不必将所有原子都定义在一个地方。如果您对应用程序进行模块化,则可以将不同原子的代码放在不同的模块中。您可以将原子分组,使其与使用它们的位置紧密相关。您可以它们放在同一位置。

这使得该模式具有解耦的特性。您无需在通用存储中配置所有原子。此外,它们也不必直接注入到您的 UI 组件包装器中。大多数框架允许您(例如通过钩子)直接与组件中的原子交互。最后,原子可以组合(在大多数实现中)。这意味着您可以在其他原子中使用原子。当底层原子发生变化时,父原子也会随之变化。您无需担心重新渲染或监听,一切都已为您管理。

它确实有一些缺点。当原子数量增加时,管理它们会变得很麻烦。你必须命名所有原子,并且必须知道它们的存在。此外,管理原子之间复杂的依赖关系结构对开发人员来说也是一项艰巨的任务。

反应性和代理

许多现代前端框架都是响应式的。当状态发生变化时,框架知道它应该重新渲染。或者换句话说,状态让框架知道它已经发生了变化。这种思维模型很像代理。代理是一个被调用的包装对象,而不是访问目标对象。这使我们能够为各种调用添加自定义行为。

代理是创建响应式且健壮的状态管理的理想选择。其基本优势在于,我们可以为状态变化添加监听器。此外,代理的值可以直接更改,无需通过函数调用更改。如果您想创建更复杂的代理,可以实现在应用状态更改之前验证更改的验证器。您甚至可以在每次状态更改之前添加多层“中间件”。您可以尽情发挥。

const store = proxy(() => ({ count: 0 }));
const listener = (c) => console.log('Count updated:', c);
store.on('count', listener);
store.count++;
// Count updated: 1
Enter fullscreen mode Exit fullscreen mode

上面的代码片段展示了一个代理示例。如您所见,我们添加了一个listener函数,用于处理 的值count发生变化的情况。现在,当我们更改 的值时countlistener就会触发该函数。请注意,此特定实现并非不可变的。您可以更改 的值。许多人更喜欢不可变的状态,因为它更不容易出现开发错误。

总结

现在你应该对状态管理的一些基础知识有了更好的理解。了解不同类型的状态以及如何管理状态是第一步。有了正确的状态管理,你可以在复杂的 Web 应用程序中取得长足进步。但这仅仅是个开始。在客户端应用程序中,还有许多(更多)重要的数据管理方法。掌握状态后,再深入研究持久化存储或缓存。

鏂囩珷鏉ユ簮锛�https://dev.to/vyckes/demystifying-state-management-2c1c
PREV
使用 CSS 的流畅界面
NEXT
向 CSS 猫头鹰选择器致敬