全栈开发, 精选文章

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 本身不会主动显示弹窗,它只是被动响应全局状态变化
  • 弹窗的配置信息(标题、描述、回调函数等)都存储在全局状态中
  • 当弹窗需要显示时,alertOpentruealertConfig 包含具体配置
  • 当弹窗关闭时,alertOpen 被设置为 false

生命周期

  1. 页面加载时,alertConfignullalertOpenfalse,弹窗不显示
  2. 点击删除按钮 → alert 函数被调用 → 全局状态更新 → AlertDialogProvider 重新渲染并显示弹窗
  3. 点击确认 → 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 状态是当前标签页独享的临时状态。