以下为知行小课学习笔记。
概述
Context 跨组件共享状态
在 Next 项目,封装 useContext。
AppContext.tsx
"use client";import React, {createContext, Dispatch, ReactNode, SetStateAction, useContext, useMemo, useState} from 'react';type State = {displayNavigation: boolean;themeMode: 'light' | 'dark';
};type AppContextProps = {state: StatesetState: 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
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
"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
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
"use client";import React, {createContext, Dispatch, ReactNode, ReducerWithoutAction, useContext, useMemo, useReducer} from 'react';
import {Action, initState, reducer, State} from "@/reducers/AppReducers";type AppContextProps = {state: Statedispatch: 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
"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;