以下为知行小课学习笔记。
概述
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;