Jotai:原子化状态管理在现代 React App中的实践

在构建复杂的 React 应用时,状态管理是一个不可避免的核心问题。从组件的本地 state 到跨组件共享的全局状态,选择一个合适的库来优雅地处理这些数据流至关重要。在我们最近的某个项目中,就选择了 Jotai 作为状态管理解决方案。

什么是 Jotai?

Jotai 是一个为 React 设计的原始(primitive)且灵活(flexible)的状态管理库。它的核心思想是 原子化(atomic)。与 Redux 或 Zustand 等库将所有状态集中存储在一个大的 store 中不同,Jotai 将状态分解为一个个独立的、微小的单元,称为 原子(atom)

你可以将 atom 想象成 React useState 的全局版本,但它更强大,支持派生状态、异步操作以及与 React Suspense 的集成。

为什么选择 Jotai?

在我们的项目中,我们需要管理诸如用户交互产生的对话框状态、聊天历史记录、当前激活的会话等多种状态。我们评估了不同的方案后,最终选择了 Jotai,主要基于以下几点考虑:

  1. 简洁性与低心智负担:Jotai 的 API 非常简洁直观。定义和使用 atom 感觉就像使用 React 内置的 Hook 一样自然,几乎没有额外的学习成本和模板代码。这使得开发者可以更专注于业务逻辑本身。
  2. 原子化的优势
    • 精确的重渲染:只有订阅了某个特定 atom 的组件才会在该 atom 更新时重渲染。这避免了大型 store 更新时可能导致的全局性、不必要的渲染,从而提升了应用的性能。
    • 模块化与可维护性:状态被分解到独立的 atom 中,使得代码更易于组织、理解和维护。每个 atom 只负责一小块状态,职责清晰。
  3. 强大的派生状态能力:Jotai 可以轻松地基于一个或多个其他 atom 创建派生 atom。这意味着你可以根据基础状态计算出新的状态,而无需手动同步,保证了状态的一致性。
  4. 开箱即用的工具集:Jotai 提供了丰富的工具函数(Utilities),例如 atomWithStorage,可以非常方便地将 atom 的状态持久化到 localStoragesessionStorage,简化了本地数据持久化的实现。

Jotai 在项目中的实践亮点

在我们的项目中,Jotai 的优势具体体现在以下几个方面:

1. 基础状态管理 ( atom )

我们使用 atom 来定义基础的状态单元。例如,管理不同类型的对话框状态:

1
2
3
4
5
6
7
8
9
10
11
12
import { atom } from "jotai";

// 定义不同类型的对话框可能的数据结构
interface DialogContent {
// ... 对话框的具体内容
}

// 使用 atom 定义存储用户触发对话框的数组
const userDialogsAtom = atom<DialogContent[]>([]);

// 使用 atom 定义存储模态对话框的数组
const modalDialogsAtom = atom<DialogContent[]>([]);

通过这种方式,不同类型的对话框状态被清晰地分离到各自的 atom 中,互不干扰。组件可以按需订阅它们需要的 atom。

2. 派生状态 (Derived Atoms)

当需要组合或基于现有状态计算新状态时,派生 atom 就派上了用场。例如,我们需要一个包含所有类型对话框的列表:

1
2
3
4
5
6
7
8
9
import { atom } from "jotai";
// (假设 userDialogsAtom 和 modalDialogsAtom 已定义)

// 定义一个派生 atom,它的值是 userDialogs 和 modalDialogs 的合并结果
const combinedDialogsAtom = atom<DialogContent[]>((get) => {
const userDialogs = get(userDialogsAtom);
const modalDialogs = get(modalDialogsAtom);
return [...userDialogs, ...modalDialogs];
});

combinedDialogsAtom 的值会自动根据 userDialogsAtommodalDialogsAtom 的变化而更新。任何订阅了 combinedDialogsAtom 的组件都会在依赖项变化时获得最新的合并列表,而无需手动监听和合并。

同样,我们使用派生 atom 来获取当前活动的聊天会话:

1
2
3
4
5
6
7
8
9
import { atom } from "jotai";
// (假设 activeHistoryIdAtom 和 chatHistoriesAtom 已定义)

// 获取当前激活的聊天会话
const currentSessionAtom = atom((get) => {
const activeId = get(activeHistoryIdAtom); // 依赖 1: 当前激活的 ID
const histories = get(chatHistoriesAtom); // 依赖 2: 所有历史记录
return histories.find(h => h.id === activeId) || null; // 根据 ID 查找
});

currentSessionAtom 依赖于 activeHistoryIdAtomchatHistoriesAtom。当用户切换会话(activeHistoryIdAtom 变化)或历史记录本身更新时(chatHistoriesAtom 变化),currentSessionAtom 会自动重新计算,确保 UI 显示的是正确的当前会话数据。

3. 状态持久化 ( atomWithStorage )

对于需要持久化的状态,如用户的聊天历史记录列表和当前选择的活动会话 ID,我们使用了 atomWithStorage 工具函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
import { atomWithStorage } from 'jotai/utils';

interface ChatHistory {
id: string;
title: string;
// ... 其他历史记录信息
}

// 将当前激活的会话 ID 持久化到 localStorage,键名为 'activeHistoryId'
const activeHistoryIdAtom = atomWithStorage<string | null>('activeHistoryId', null);

// 将所有聊天历史记录列表持久化到 localStorage,键名为 'chatHistories'
const chatHistoriesAtom = atomWithStorage<ChatHistory[]>('chatHistories', []);

只需一行代码,Jotai 就处理了与 localStorage 的读写同步。这极大地简化了状态持久化的逻辑,使开发者无需编写繁琐的 localStorage.getItemlocalStorage.setItem 调用以及相关的 effect 逻辑。

结论

Jotai 以其原子化、简洁和灵活的特性,为我们的 React 项目提供了一个高效且易于维护的状态管理方案。它的核心理念与 React Hooks 高度契合,使得状态管理变得更加直观和分散化。通过精确的重渲染优化、强大的派生状态能力以及便捷的持久化工具,Jotai 帮助我们构建了性能更好、代码结构更清晰的应用。如果你正在寻找一个轻量级、现代化的 React 状态管理库,Jotai 绝对值得一试。