文章
nextjs状态管理工具
组件:npm install zustand immer
代码:
组件:npm install zustand immer
代码:
import { create } from "zustand";
import { StateCreator } from "zustand/vanilla";
import { immer } from "zustand/middleware/immer";
import { persist, createJSONStorage } from "zustand/middleware";
type ConfigType<T> = {
name?: string; // 持久化存储的名称
storage?: Storage; // 存储方式(localStorage, sessionStorage等)
skipPersist?: boolean; // 是否跳过持久化
excludeFromPersist?: Array<keyof T>; // 要排除的持久化字段
};
// // T 是一个对象类型,表示 store 的状态类型
const createStore = <T extends object>(
storeCreator: StateCreator<T, [["zustand/immer", never]], []>, // 状态创建器函数
config?: ConfigType<T> // 配置选项
) => {
// 解构配置
const { name, storage, skipPersist, excludeFromPersist } = config || {};
// 让状态更新支持不可变更新语法
const immerStore = immer(storeCreator);
if (skipPersist) {
// 不需要持久化 → 直接创建普通 store
return create<T>()(immerStore);
}
// 需要持久化 → 创建持久化 store
return create<T>()(
persist(immerStore, {
name: name || "zustand-store",
storage: createJSONStorage(() => storage || localStorage),
// partialize 是一个过滤函数,它确保只有指定的字段会被持久化到存储中
partialize: (state) =>
Object.fromEntries(
Object.entries(state).filter(
([key]) => !excludeFromPersist?.includes(key as keyof T),
)
)
}));
}
export { createStore };import { create } from "zustand";
import { StateCreator } from "zustand/vanilla";
import { immer } from "zustand/middleware/immer";
import { persist, createJSONStorage } from "zustand/middleware";
type ConfigType<T> = {
name?: string; // 持久化存储的名称
storage?: Storage; // 存储方式(localStorage, sessionStorage等)
skipPersist?: boolean; // 是否跳过持久化
excludeFromPersist?: Array<keyof T>; // 要排除的持久化字段
};
// // T 是一个对象类型,表示 store 的状态类型
const createStore = <T extends object>(
storeCreator: StateCreator<T, [["zustand/immer", never]], []>, // 状态创建器函数
config?: ConfigType<T> // 配置选项
) => {
// 解构配置
const { name, storage, skipPersist, excludeFromPersist } = config || {};
// 让状态更新支持不可变更新语法
const immerStore = immer(storeCreator);
if (skipPersist) {
// 不需要持久化 → 直接创建普通 store
return create<T>()(immerStore);
}
// 需要持久化 → 创建持久化 store
return create<T>()(
persist(immerStore, {
name: name || "zustand-store",
storage: createJSONStorage(() => storage || localStorage),
// partialize 是一个过滤函数,它确保只有指定的字段会被持久化到存储中
partialize: (state) =>
Object.fromEntries(
Object.entries(state).filter(
([key]) => !excludeFromPersist?.includes(key as keyof T),
)
)
}));
}
export { createStore };
🎯 实际使用示例
创建一个用户状态 Store:
// 定义状态类型
interface UserState {
user: { name: string; email: string } | null;
isLoggedIn: boolean;
login: (user: { name: string; email: string }) => void;
logout: () => void;
}
// 创建状态创建器
const userStoreCreator: StateCreator<UserState, [["zustand/immer", never]], []> = (set) => ({
user: null,
isLoggedIn: false,
login: (userData) =>
set((state) => {
state.user = userData;
state.isLoggedIn = true;
}),
logout: () =>
set((state) => {
state.user = null;
state.isLoggedIn = false;
})
});
// 使用工厂函数创建 store
const useUserStore = createStore(
userStoreCreator,
{
name: "user-storage", // 持久化名称
excludeFromPersist: ["tempData"] // 不持久化的字段
}
);// 定义状态类型
interface UserState {
user: { name: string; email: string } | null;
isLoggedIn: boolean;
login: (user: { name: string; email: string }) => void;
logout: () => void;
}
// 创建状态创建器
const userStoreCreator: StateCreator<UserState, [["zustand/immer", never]], []> = (set) => ({
user: null,
isLoggedIn: false,
login: (userData) =>
set((state) => {
state.user = userData;
state.isLoggedIn = true;
}),
logout: () =>
set((state) => {
state.user = null;
state.isLoggedIn = false;
})
});
// 使用工厂函数创建 store
const useUserStore = createStore(
userStoreCreator,
{
name: "user-storage", // 持久化名称
excludeFromPersist: ["tempData"] // 不持久化的字段
}
);弹窗状态管理
import { createStore } from "./createStore";
// 定义弹窗的配置选项类型
type AlertConfig = {
title?: string; // 弹窗标题(可选)
description?: string; // 弹窗描述(可选)
confirmLabel?: string; // 确认按钮文字(可选)
cancelLabel?: string; // 取消按钮文字(可选)
onConfirm?: () => void; // 点击确认的回调函数(可选)
onCancel?: () => void; // 点击取消的回调函数(可选)
};
// 定义 store 的状态部分
type State = {
alertOpen: boolean; // 弹窗是否打开
alertConfig: AlertConfig | null; // 当前弹窗的配置,null表示没有弹窗
};
// 定义 store 的操作方法部分
type Actions = {
updateAlertOpen: (is: boolean) => void; // 更新弹窗打开/关闭状态
showAlert: (config: AlertConfig) => void; // 显示弹窗,传入配置
};
// 将状态和操作合并成完整的 store 类型
type Store = State & Actions;
const useGlobalStore = createStore<Store>(
// 第一个参数:store 的初始状态和方法
// set 是 Zustand 内部提供的一个特殊函数,用于安全地更新 store 中的状态。它确保状态更新是可追踪的,并且能正确触发订阅了该状态的组件重新渲染。
// 这个函数是 Zustand 要求的格式
(set) => ({ // Zustand 传入 set 函数
// 初始状态
alertOpen: false, // 初始时弹窗关闭
alertConfig: null, // 初始时没有弹窗配置
// 操作方法
// 定义方法,这些方法内部使用 set 来更新状态
updateAlertOpen: (is) =>
// set传入一个函数(推荐,用于基于当前状态的更新)
set((state) => { // 调用 Zustand 的 set 函数
// 修改状态
state.alertOpen = is; // 设置弹窗开关状态
if (!is) state.alertConfig = null; // 如果关闭弹窗,清空配置
}),
showAlert: (config) =>
// set传入一个函数(推荐,用于基于当前状态的更新)
set((state) => {
state.alertOpen = true; // 打开弹窗
state.alertConfig = config; // 设置弹窗配置
}),
}),
// 第二个参数:持久化配置
{
name: "global-strore", // 持久化存储的键名
excludeFromPersist: ["alertOpen"] // 不需要持久化的状态字段
}
);
const alert = (config: AlertConfig) => {
// 直接获取 store 的当前状态,调用其中的 showAlert 方法
useGlobalStore.getState().showAlert(config);
};
export { useGlobalStore, alert };构建弹窗组件Provider
import { useGlobalStore } from "@/lib/use-global-store"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "./alert-dialog";
const AlertDialogProvider = () => {
const { alertConfig, alertOpen, updateAlertOpen } = useGlobalStore();
if (!alertConfig) return null;
const handleConfirm = () => {
if (alertConfig.onConfirm) {
alertConfig.onConfirm();
}
updateAlertOpen(false);
};
const handleCancel = () => {
if (alertConfig.onCancel) {
alertConfig.onCancel();
}
updateAlertOpen(false);
}
return (
<AlertDialog open={alertOpen} onOpenChange={updateAlertOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{alertConfig.title || "Confirmation Required"}
</AlertDialogTitle>
<AlertDialogDescription>
{alertConfig.description || "Are you sure you want to perform this action?"}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={handleCancel}>
{alertConfig.cancelLabel || "Cancel"}
</AlertDialogCancel>
<AlertDialogAction onClick={handleConfirm}>
{alertConfig.confirmLabel || "Continue"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
export { AlertDialogProvider };AlertDialogProvider 的角色
AlertDialogProvider 组件的作用:
- 监听者:监听全局状态变化
- 渲染器:根据状态决定是否显示弹窗
- 事件处理器:处理确认和取消按钮的点击事件
添加到全局provider:
"use client";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactNode } from "react";
import { Toaster } from "./ui/sonner";
import { AlertDialogProvider } from "./ui/alert-dialog-provier";
const queryClient = new QueryClient();
type ProvidersProps = {
children: ReactNode;
};
const Providers = ({ children }: ProvidersProps) => {
return (
<QueryClientProvider client={queryClient}>
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<Toaster />
<AlertDialogProvider />
{children}
</NextThemesProvider>
</QueryClientProvider>
)
};
export { Providers };应用:
<Button
className="size-6"
variant="ghost"
size="icon"
onClick={() => {
alert({
onConfirm: () => deleteCategoryMution.mutate(item.id),
});
}}
>
<Trash />
</Button>数据流向
CategoryCards
↓ (调用 alert 函数)
Global Store (更新 alertConfig 和 alertOpen)
↓ (状态变化)
AlertDialogProvider (重新渲染并显示弹窗)
↓ (用户点击确认按钮)
handleConfirm 执行 onConfirm 回调
↓ (执行删除操作)
deleteCategoryMution.mutate(item.id)关键点
AlertDialogProvider本身不会主动显示弹窗,它只是被动响应全局状态变化- 弹窗的配置信息(标题、描述、回调函数等)都存储在全局状态中
- 当弹窗需要显示时,
alertOpen为true,alertConfig包含具体配置 - 当弹窗关闭时,
alertOpen被设置为false
生命周期
- 页面加载时,
alertConfig为null,alertOpen为false,弹窗不显示 - 点击删除按钮 →
alert函数被调用 → 全局状态更新 →AlertDialogProvider重新渲染并显示弹窗 - 点击确认 →
handleConfirm执行 → 删除操作 →updateAlertOpen(false)→ 弹窗关闭
这就是典型的状态驱动UI模式,AlertDialogProvider 充当了状态到UI的桥梁。
React 状态监听和重新渲染机制
1. 状态订阅机制
// 在 AlertDialogProvider 组件中
const { alertConfig, alertOpen, updateAlertOpen } = useGlobalStore();当组件使用 useGlobalStore 时,它实际上订阅了 store 中的状态变化。
2. 状态变化触发重新渲染
// 当 alert 函数更新状态时
alert({
onConfirm: () => deleteCategoryMution.mutate(item.id),
});
// → 这会更新全局 store 中的 alertConfig 和 alertOpen
// → 所有订阅了这些状态的组件都会重新渲染3. 具体流程
状态变化 → 通知订阅者 → 组件重新渲染 → UI 更新4. 与定时器监控的区别
这不是一个循环执行的定时器:
- 不是每隔几毫秒检查一次状态
- 而是事件驱动的:状态改变时立即通知组件
- 更高效,只在需要时才重新渲染
5. 类比理解
就像订阅报纸:
- 你订阅了报纸(组件订阅状态)
- 报社有新报纸时(状态改变)
- 邮递员立即送报(状态管理库通知组件)
- 你立即收到新报纸(组件重新渲染)
6. Zustand 的内部机制
// Zustand 内部类似这样工作
const store = {
state: { alertOpen: false, alertConfig: null },
listeners: [], // 订阅者列表
setState(newState) {
this.state = { ...this.state, ...newState };
this.listeners.forEach(listener => listener()); // 通知所有订阅者
}
};所以是状态变化驱动UI更新,而不是UI不断检查状态变化。这种机制既高效又响应迅速。
状态是针对当前浏览器标签页的,更准确地说是:
状态的作用范围
1. 当前浏览器标签页 + 内存
- 状态存储在浏览器标签页的 JavaScript 内存中
- 每个打开的标签页都有独立的状态副本
- 关闭标签页时状态丢失
2. 具体场景说明
标签页A: 状态 alertOpen = false
标签页B: 状态 alertOpen = false (独立的副本)
在标签页A中调用 alert() → 只影响标签页A的状态
标签页B的状态保持不变3. 与其他存储方式的对比
| 存储方式 | 作用范围 | 持久性 | 适用场景 |
|---|---|---|---|
| Zustand状态 | 单个标签页 | 临时,刷新丢失 | 组件间通信、UI状态管理 |
| localStorage | 同一浏览器 | 持久,跨会话 | 用户偏好设置 |
| sessionStorage | 单个标签页 | 临时,关闭标签页丢失 | 临时数据 |
| Cookie | 同一浏览器 | 可配置 | 用户认证、跨标签页通信 |
4. 实际影响
- 如果你在标签页A中打开了弹窗状态
- 在另一个标签页B中打开同一页面,弹窗状态是关闭的
- 刷新页面后,状态重置为初始值
- 不同用户(不同浏览器)完全独立
5. 如果需要跨标签页共享
需要使用 localStorage + storage 事件 或 BroadcastChannel API:
// 示例:跨标签页通信
window.addEventListener('storage', (e) => {
if (e.key === 'sharedAlertState') {
// 处理其他标签页的状态变化
}
});所以你的 Zustand 状态是当前标签页独享的临时状态。