【React】全局状态管理(Context, Reducer)

以下为知行小课学习笔记。

概述

Context 跨组件共享状态

在 Next 项目,封装 useContext。

AppContext.tsx

tsx 复制代码
"use client";

import React, {createContext, Dispatch, ReactNode, SetStateAction, useContext, useMemo, useState} from 'react';

type State = {
    displayNavigation: boolean;
    themeMode: 'light' | 'dark';
};

type AppContextProps = {
    state: State
    setState: Dispatch<SetStateAction<State>>
}

const AppContext = createContext<AppContextProps>(null!)

export function useAppContext() {
    return useContext(AppContext)
}

export default function AppContextProvider({children}: { children: ReactNode }) {
    const [state, setState] = useState<State>({displayNavigation: true, themeMode: 'light'})
    // 性能优化
    const contextValue = useMemo(() => {
        return {state, setState}
    }, [state, setState])
    return <AppContext.Provider value={contextValue}>
        {children}
    </AppContext.Provider>
}

使用自定义封装的 useContext 和 ContextProvider 。

layouts.tsx

tsx 复制代码
import type {Metadata} from "next";
import "./globals.css";
import AppContextProvider from "@/components/AppContext";

export const metadata: Metadata = {
  title: "XIU-GPT",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="zh">
      <body>
       <AppContextProvider>{children}</AppContextProvider>
      </body>
    </html>
  );
}

Toolbar.tsx

tsx 复制代码
"use client"
import React from 'react';
import Button from "@/components/common/Button";
import {useAppContext} from "@/components/AppContext";
import {MdDarkMode, MdInfo, MdLightMode} from "react-icons/md";

function Toolbar() {
    const {state: {themeMode}, setState} = useAppContext()
    return (
        <div className={`absolute left-0 right-0 bottom-0 dark:bg-gray-800 dark:border-gray-800 border-t-2 flex p-2 justify-between`}>
            <Button icon={themeMode === 'dark' ? MdDarkMode : MdLightMode} variant="text"
                    onClick={() => {
                        setState((prevState) => {
                            return {
                                ...prevState,
                                themeMode: prevState.themeMode === 'dark' ? 'light' : 'dark'
                            }
                        })
                    }}/>
            <Button icon={MdInfo} variant="text"/>
        </div>
    );
}

export default Toolbar;

Reducer 复杂状态管理

setState 如果每次执行只是更新少量 state ,但都需要重新 set 所有 state,更新状态会变得繁琐,尤其是在 state 层级较多的情况下。useReducer 抽离 setState 逻辑,更好管理状态。

reducers/AppReducers.ts

ts 复制代码
import {ReducerWithoutAction} from "react";

export type State = {
    displayNavigation: boolean;
    themeMode: 'light' | 'dark';
};

export enum ActionType {
    UPDATE = "UPDATE"
}

type UpdateAction = {
    type: ActionType.UPDATE;
    field: string;
    value: any;
}

export type Action = UpdateAction;

export const initState: State = {
    displayNavigation: true,
    themeMode: 'light'
}

export function reducer(state: State, action: Action) {
    switch (action.type) {
        case ActionType.UPDATE:
            return { ...state, [action.field]: action.value}
        default: throw new Error(`Unhandled action type: ${action.type}`)
    }
}

AppContext.tsx

tsx 复制代码
"use client";

import React, {createContext, Dispatch, ReactNode, ReducerWithoutAction, useContext, useMemo, useReducer} from 'react';
import {Action, initState, reducer, State} from "@/reducers/AppReducers";


type AppContextProps = {
    state: State
    dispatch: Dispatch<Action>
}

const AppContext = createContext<AppContextProps>(null!)

export function useAppContext() {
    return useContext(AppContext)
}

export default function AppContextProvider({children}: { children: ReactNode }) {
    const [state, dispatch] = useReducer(reducer as ReducerWithoutAction<any>, initState, () => {
        return initState
    })
    const contextValue = useMemo(() => {
        return {state, dispatch}
    }, [state, dispatch])
    return <AppContext.Provider value={contextValue}>
        {children}
    </AppContext.Provider>
}

Toolbar.tsx

tsx 复制代码
"use client"
import React from 'react';
import Button from "@/components/common/Button";
import {useAppContext} from "@/components/AppContext";
import {MdDarkMode, MdInfo, MdLightMode} from "react-icons/md";
import {ActionType} from "@/reducers/AppReducers";

function Toolbar() {
    const {state: {themeMode}, dispatch} = useAppContext()
    return (
        <div className={`absolute left-0 right-0 bottom-0 dark:bg-gray-800 dark:border-gray-800 border-t-2 flex p-2 justify-between`}>
            <Button icon={themeMode === 'dark' ? MdDarkMode : MdLightMode} variant="text"
                    onClick={() => {
                        dispatch({
                            type: ActionType.UPDATE,
                            field: "themeMode",
                            value: themeMode === 'dark' ? 'light' : 'dark'
                        })
                    }}/>
            <Button icon={MdInfo} variant="text"/>
        </div>
    );
}

export default Toolbar;
相关推荐
kyriewen2 分钟前
前端错误监控最全指南:捕获 JS 异常、Promise 拒绝、资源加载失败,附上报代码
前端·javascript·监控
大家的林语冰17 分钟前
ESLint 近期动态大全,新版本正式发布,antfu 大佬推荐的插件也更新了!
前端·javascript·前端工程化
胡志辉2 小时前
深入浅出 call、apply、bind
前端·javascript·后端
十九画生4 小时前
parentID ``` JavaScript 是区分大小写的,所以这两个不是同一个字段。 第二,`parent` 没有声明。 应该先写: `
javascript
怕浪猫5 小时前
Electron 开发实战(十六):总结与展望|生态现状、框架对比、行业趋势与学习指南
前端·javascript·electron
ZengLiangYi6 小时前
批量导入 1000 条对话的性能优化实战
javascript·后端·架构
竹林8186 小时前
用 wagmi v2 + viem 监听合约事件时踩的坑,我花了两天才把"遗漏事件"修好
javascript
YFF菲菲兔6 小时前
finishConcurrentRender 源码解析
react.js
小花酱酱6 小时前
QQ群里只有你一个人?邪门歪道破局之路——AstrBot
javascript
bonechips6 小时前
JS 数组指南:从内存原理到二维矩阵
前端·javascript