Uurige React Context API tĂ€iustatud mustreid, sealhulgas liitkomponente, dĂŒnaamilisi kontekste ja optimeeritud jĂ”udlustehnikaid keeruka olekuhalduse jaoks.
TĂ€iustatud React Context API mustrid olekuhalduseks
React Context API pakub vĂ”imsat mehhanismi oleku jagamiseks ĂŒle kogu rakenduse ilma prop'ide "lĂ€bi puurimiseta" (prop drilling). Kuigi pĂ”hikĂ€sitlus on lihtne, nĂ”uab selle tĂ€ieliku potentsiaali Ă€rakasutamine tĂ€iustatud mustrite mĂ”istmist, mis suudavad toime tulla keerukate olekuhalduse stsenaariumitega. See artikkel uurib mitmeid neist mustritest, pakkudes praktilisi nĂ€iteid ja rakendatavaid teadmisi, et tĂ”sta teie Reacti arendustaset.
PÔhilise Context API piirangute mÔistmine
Enne tÀiustatud mustritesse sukeldumist on oluline teadvustada pÔhilise Context API piiranguid. Kuigi see sobib lihtsa, globaalselt kÀttesaadava oleku jaoks, vÔib see muutuda kohmakaks ja ebatÔhusaks keerukates rakendustes, kus olek sageli muutub. Iga komponent, mis tarbib konteksti, renderdatakse uuesti iga kord, kui konteksti vÀÀrtus muutub, isegi kui komponent ei sÔltu konkreetsest oleku osast, mida uuendati. See vÔib pÔhjustada jÔudluse kitsaskohti.
Muster 1: Liitkomponendid kontekstiga
Liitkomponendi muster tÀiustab Context API-d, luues seotud komponentide komplekti, mis jagavad kaudselt olekut ja loogikat konteksti kaudu. See muster soodustab taaskasutatavust ja lihtsustab tarbijate jaoks API-d. See vÔimaldab keeruka loogika kapseldada lihtsa implementatsiooniga.
NĂ€ide: Sakikomponent (Tab Component)
Illustreerime seda sakikomponendi nÀitel. Selle asemel, et edastada prop'e lÀbi mitme kihi, suhtlevad Tab komponendid kaudselt lÀbi jagatud konteksti.
// TabContext.js
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface TabContextType {
activeTab: string;
setActiveTab: (tab: string) => void;
}
const TabContext = createContext(undefined);
interface TabProviderProps {
children: ReactNode;
defaultTab: string;
}
export const TabProvider: React.FC = ({ children, defaultTab }) => {
const [activeTab, setActiveTab] = useState(defaultTab);
const value: TabContextType = {
activeTab,
setActiveTab,
};
return {children} ;
};
export const useTabContext = () => {
const context = useContext(TabContext);
if (!context) {
throw new Error('useTabContext must be used within a TabProvider');
}
return context;
};
// TabList.js
import React, { ReactNode } from 'react';
interface TabListProps {
children: ReactNode;
}
export const TabList: React.FC = ({ children }) => {
return {children};
};
// Tab.js
import React, { ReactNode } from 'react';
import { useTabContext } from './TabContext';
interface TabProps {
label: string;
children: ReactNode;
}
export const Tab: React.FC = ({ label, children }) => {
const { activeTab, setActiveTab } = useTabContext();
const isActive = activeTab === label;
const handleClick = () => {
setActiveTab(label);
};
return (
);
};
// TabPanel.js
import React, { ReactNode } from 'react';
import { useTabContext } from './TabContext';
interface TabPanelProps {
label: string;
children: ReactNode;
}
export const TabPanel: React.FC = ({ label, children }) => {
const { activeTab } = useTabContext();
const isActive = activeTab === label;
return (
{isActive && children}
);
};
// Kasutamine
import { TabProvider, TabList, Tab, TabPanel } from './components/Tabs';
function App() {
return (
Tab 1
Tab 2
Tab 3
Content for Tab 1
Content for Tab 2
Content for Tab 3
);
}
export default App;
Eelised:
- Lihtsustatud API tarbijatele: Kasutajad peavad muretsema ainult
Tab,TabListjaTabPanelpÀrast. - Kaudne oleku jagamine: Komponendid pÀÀsevad automaatselt juurde jagatud olekule ja uuendavad seda.
- Parem taaskasutatavus:
Tabkomponenti saab hÔlpsasti taaskasutada erinevates kontekstides.
Muster 2: DĂŒnaamilised kontekstid
MĂ”nes stsenaariumis vĂ”ib teil vaja minna erinevaid konteksti vÀÀrtusi, mis pĂ”hinevad komponendi asukohal komponendipuus vĂ”i muudel dĂŒnaamilistel teguritel. DĂŒnaamilised kontekstid vĂ”imaldavad teil luua ja pakkuda konteksti vÀÀrtusi, mis varieeruvad vastavalt konkreetsetele tingimustele.
NĂ€ide: Teemad dĂŒnaamiliste kontekstidega
Kujutage ette teemasĂŒsteemi, kus soovite pakkuda erinevaid teemasid vastavalt kasutaja eelistustele vĂ”i rakenduse osale, kus nad viibivad. Saame luua lihtsustatud nĂ€ite heleda ja tumeda teemaga.
// ThemeContext.js
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface Theme {
background: string;
color: string;
}
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
}
const defaultTheme: Theme = {
background: 'white',
color: 'black'
};
const darkTheme: Theme = {
background: 'black',
color: 'white'
};
const ThemeContext = createContext({
theme: defaultTheme,
toggleTheme: () => {}
});
interface ThemeProviderProps {
children: ReactNode;
}
export const ThemeProvider: React.FC = ({ children }) => {
const [isDarkTheme, setIsDarkTheme] = useState(false);
const theme = isDarkTheme ? darkTheme : defaultTheme;
const toggleTheme = () => {
setIsDarkTheme(!isDarkTheme);
};
const value: ThemeContextType = {
theme,
toggleTheme,
};
return {children} ;
};
export const useTheme = () => {
return useContext(ThemeContext);
};
// Kasutamine
import { useTheme, ThemeProvider } from './ThemeContext';
function MyComponent() {
const { theme, toggleTheme } = useTheme();
return (
This is a themed component.
);
}
function App() {
return (
);
}
export default App;
Selles nĂ€ites mÀÀrab ThemeProvider dĂŒnaamiliselt teema vastavalt isDarkTheme olekule. Komponendid, mis kasutavad useTheme hook'i, renderdatakse automaatselt uuesti, kui teema muutub.
Muster 3: Kontekst koos useReducer'iga keeruka oleku jaoks
Keeruka olekuloogika haldamiseks on Context API kombineerimine useReducer'iga suurepÀrane lÀhenemine. useReducer pakub struktureeritud viisi oleku uuendamiseks tegevuste (actions) pÔhjal ja Context API vÔimaldab teil seda olekut ja dispatch-funktsiooni kogu rakenduses jagada.
NĂ€ide: Lihtne ĂŒlesannete nimekiri
// TodoContext.js
import React, { createContext, useContext, useReducer, ReactNode } from 'react';
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface TodoState {
todos: Todo[];
}
type TodoAction =
| { type: 'ADD_TODO'; text: string }
| { type: 'TOGGLE_TODO'; id: number }
| { type: 'DELETE_TODO'; id: number };
interface TodoContextType {
state: TodoState;
dispatch: React.Dispatch;
}
const initialState: TodoState = {
todos: [],
};
const todoReducer = (state: TodoState, action: TodoAction): TodoState => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, { id: Date.now(), text: action.text, completed: false }],
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map((todo) =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
),
};
case 'DELETE_TODO':
return {
...state,
todos: state.todos.filter((todo) => todo.id !== action.id),
};
default:
return state;
}
};
const TodoContext = createContext(undefined);
interface TodoProviderProps {
children: ReactNode;
}
export const TodoProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(todoReducer, initialState);
const value: TodoContextType = {
state,
dispatch,
};
return {children} ;
};
export const useTodo = () => {
const context = useContext(TodoContext);
if (!context) {
throw new Error('useTodo must be used within a TodoProvider');
}
return context;
};
// Kasutamine
import { useTodo, TodoProvider } from './TodoContext';
function TodoList() {
const { state, dispatch } = useTodo();
return (
{state.todos.map((todo) => (
-
{todo.text}
))}
);
}
function AddTodo() {
const { dispatch } = useTodo();
const [text, setText] = React.useState('');
const handleSubmit = (e) => {
e.preventDefault();
dispatch({ type: 'ADD_TODO', text });
setText('');
};
return (
);
}
function App() {
return (
);
}
export default App;
See muster tsentraliseerib olekuhalduse loogika reducer'i sisse, muutes selle mÔistmise ja testimise lihtsamaks. Komponendid saavad saata tegevusi oleku uuendamiseks, ilma et peaksid olekut otse haldama.
Muster 4: Optimeeritud konteksti uuendused `useMemo` ja `useCallback` abil
Nagu varem mainitud, on Context API peamine jÔudlusprobleem tarbetud uuesti renderdamised. useMemo ja useCallback kasutamine aitab vÀltida neid uuesti renderdamisi, tagades, et uuendatakse ainult vajalikke konteksti vÀÀrtuse osi ja et funktsiooniviited jÀÀvad stabiilseks.
NĂ€ide: Teemakonteksti optimeerimine
// OptimizedThemeContext.js
import React, { createContext, useContext, useState, useMemo, useCallback, ReactNode } from 'react';
interface Theme {
background: string;
color: string;
}
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
}
const defaultTheme: Theme = {
background: 'white',
color: 'black'
};
const darkTheme: Theme = {
background: 'black',
color: 'white'
};
const ThemeContext = createContext({
theme: defaultTheme,
toggleTheme: () => {}
});
interface ThemeProviderProps {
children: ReactNode;
}
export const ThemeProvider: React.FC = ({ children }) => {
const [isDarkTheme, setIsDarkTheme] = useState(false);
const theme = isDarkTheme ? darkTheme : defaultTheme;
const toggleTheme = useCallback(() => {
setIsDarkTheme(!isDarkTheme);
}, [isDarkTheme]);
const value: ThemeContextType = useMemo(() => ({
theme,
toggleTheme,
}), [theme, toggleTheme]);
return {children} ;
};
export const useTheme = () => {
return useContext(ThemeContext);
};
Selgitus:
useCallbackmemoiseeribtoggleThemefunktsiooni. See tagab, et funktsiooniviide muutub ainult siis, kuiisDarkThememuutub, vÀltides tarbetuid uuesti renderdamisi komponentides, mis sÔltuvad ainulttoggleThemefunktsioonist.useMemomemoiseerib konteksti vÀÀrtuse. See tagab, et konteksti vÀÀrtus muutub ainult siis, kui muutub kasthemevÔitoggleThemefunktsioon, vÀltides veelgi tarbetuid uuesti renderdamisi.
Ilma useCallback'ita loodaks toggleTheme funktsioon uuesti iga ThemeProvider'i renderdamise korral, pÔhjustades value muutumise ja kÀivitades uuesti renderdamise kÔigis tarbivates komponentides, isegi kui teema ise poleks muutunud. useMemo tagab, et uus value luuakse ainult siis, kui selle sÔltuvused (theme vÔi toggleTheme) muutuvad.
Muster 5: Konteksti selektorid
Konteksti selektorid vÔimaldavad komponentidel tellida ainult konkreetseid osi konteksti vÀÀrtusest. See hoiab Àra tarbetud uuesti renderdamised, kui muud konteksti osad muutuvad. Selle saavutamiseks saab kasutada teeke nagu `use-context-selector` vÔi kohandatud implementatsioone.
NĂ€ide kohandatud konteksti selektori kasutamisest
// useCustomContextSelector.js
import { useContext, useState, useRef, useEffect } from 'react';
function useCustomContextSelector(
context: React.Context,
selector: (value: T) => S
): S {
const value = useContext(context);
const [selected, setSelected] = useState(() => selector(value));
const latestSelector = useRef(selector);
latestSelector.current = selector;
useEffect(() => {
let didUnmount = false;
let lastSelected = selected;
const subscription = () => {
if (didUnmount) {
return;
}
const nextSelected = latestSelector.current(value);
if (!Object.is(lastSelected, nextSelected)) {
lastSelected = nextSelected;
setSelected(nextSelected);
}
};
// Tavaliselt telliksite siin konteksti muudatusi. Kuna see on lihtsustatud
// nÀide, kutsume lihtsalt kohe subscription funktsiooni vÀlja initsialiseerimiseks.
subscription();
return () => {
didUnmount = true;
// TĂŒhista siin konteksti muudatuste tellimus, kui see on asjakohane.
};
}, [value]); // KÀivita efekt uuesti, kui konteksti vÀÀrtus muutub
return selected;
}
export default useCustomContextSelector;
// ThemeContext.js (LĂŒhendatult selguse huvides)
import React, { createContext, useState, ReactNode } from 'react';
interface Theme {
background: string;
color: string;
}
interface ThemeContextType {
theme: Theme;
setTheme: (newTheme: Theme) => void;
}
const ThemeContext = createContext(undefined);
interface ThemeProviderProps {
children: ReactNode;
initialTheme: Theme;
}
export const ThemeProvider: React.FC = ({ children, initialTheme }) => {
const [theme, setTheme] = useState(initialTheme);
const value: ThemeContextType = {
theme,
setTheme
};
return {children} ;
};
export const useThemeContext = () => {
const context = React.useContext(ThemeContext);
if (!context) {
throw new Error("useThemeContext must be used within a ThemeProvider");
}
return context;
};
export default ThemeContext;
// Kasutamine
import useCustomContextSelector from './useCustomContextSelector';
import ThemeContext, { ThemeProvider, useThemeContext } from './ThemeContext';
function BackgroundComponent() {
const background = useCustomContextSelector(ThemeContext, (context) => context.theme.background);
return Background;
}
function ColorComponent() {
const color = useCustomContextSelector(ThemeContext, (context) => context.theme.color);
return Color;
}
function App() {
const { theme, setTheme } = useThemeContext();
const toggleTheme = () => {
setTheme({ background: theme.background === 'white' ? 'black' : 'white', color: theme.color === 'black' ? 'white' : 'black' });
};
return (
);
}
export default App;
Selles nÀites renderdatakse BackgroundComponent uuesti ainult siis, kui teema background omadus muutub, ja ColorComponent renderdatakse uuesti ainult siis, kui color omadus muutub. See vÀldib tarbetuid uuesti renderdamisi, kui kogu konteksti vÀÀrtus muutub.
Muster 6: Tegevuste eraldamine olekust
Suuremate rakenduste puhul kaaluge konteksti vÀÀrtuse eraldamist kaheks eraldiseisvaks kontekstiks: ĂŒks oleku ja teine tegevuste (dispatch-funktsioonide) jaoks. See vĂ”ib parandada koodi organiseeritust ja testitavust.
NĂ€ide: Ălesannete nimekiri eraldatud oleku ja tegevuse kontekstidega
// TodoStateContext.js
import React, { createContext, useContext, useReducer, ReactNode } from 'react';
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface TodoState {
todos: Todo[];
}
const initialState: TodoState = {
todos: [],
};
const TodoStateContext = createContext(initialState);
interface TodoStateProviderProps {
children: ReactNode;
}
export const TodoStateProvider: React.FC = ({ children }) => {
const [state] = useReducer(todoReducer, initialState);
return {children} ;
};
export const useTodoState = () => {
return useContext(TodoStateContext);
};
// TodoActionContext.js
import React, { createContext, useContext, Dispatch, ReactNode } from 'react';
type TodoAction =
| { type: 'ADD_TODO'; text: string }
| { type: 'TOGGLE_TODO'; id: number }
| { type: 'DELETE_TODO'; id: number };
const TodoActionContext = createContext | undefined>(undefined);
interface TodoActionProviderProps {
children: ReactNode;
}
export const TodoActionProvider: React.FC = ({children}) => {
const [, dispatch] = useReducer(todoReducer, initialState);
return {children} ;
};
export const useTodoDispatch = () => {
const dispatch = useContext(TodoActionContext);
if (!dispatch) {
throw new Error('useTodoDispatch must be used within a TodoActionProvider');
}
return dispatch;
};
// todoReducer.js
export const todoReducer = (state: TodoState, action: TodoAction): TodoState => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, { id: Date.now(), text: action.text, completed: false }],
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map((todo) =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
),
};
case 'DELETE_TODO':
return {
...state,
todos: state.todos.filter((todo) => todo.id !== action.id),
};
default:
return state;
}
};
// Kasutamine
import { useTodoState, TodoStateProvider } from './TodoStateContext';
import { useTodoDispatch, TodoActionProvider } from './TodoActionContext';
function TodoList() {
const state = useTodoState();
return (
{state.todos.map((todo) => (
-
{todo.text}
))}
);
}
function TodoActions({ todo }) {
const dispatch = useTodoDispatch();
return (
<>
>
);
}
function AddTodo() {
const dispatch = useTodoDispatch();
const [text, setText] = React.useState('');
const handleSubmit = (e) => {
e.preventDefault();
dispatch({ type: 'ADD_TODO', text });
setText('');
};
return (
);
}
function App() {
return (
);
}
export default App;
See eraldamine vÔimaldab komponentidel tellida ainult seda konteksti, mida nad vajavad, vÀhendades tarbetuid uuesti renderdamisi. Samuti muudab see reducer'i ja iga komponendi eraldi testimise lihtsamaks. Lisaks on oluline provider'ite mÀhkimise jÀrjekord. ActionProvider peab mÀhkima StateProvider'it.
Parimad praktikad ja kaalutlused
- Kontekst ei tohiks asendada kÔiki olekuhalduse teeke: VÀga suurte ja keerukate rakenduste jaoks vÔivad spetsiaalsed olekuhalduse teegid nagu Redux vÔi Zustand olla siiski parem valik.
- VĂ€ltige ĂŒlekontekstualiseerimist: Mitte iga olekuosa ei pea olema kontekstis. Kasutage konteksti arukalt tĂ”eliselt globaalse vĂ”i laialt jagatud oleku jaoks.
- JÔudluse testimine: MÔÔtke alati oma kontekstikasutuse mÔju jÔudlusele, eriti kui tegemist on sageli uueneva olekuga.
- Koodi tĂŒkeldamine (Code Splitting): Context API kasutamisel kaaluge oma rakenduse koodi tĂŒkeldamist vĂ€iksemateks osadeks. See on eriti oluline, kui vĂ€ike muudatus olekus pĂ”hjustab suure osa rakenduse uuesti renderdamise.
KokkuvÔte
React Context API on mitmekĂŒlgne tööriist olekuhalduseks. MĂ”istes ja rakendades neid tĂ€iustatud mustreid, saate tĂ”husalt hallata keerukat olekut, optimeerida jĂ”udlust ning ehitada hooldatavamaid ja skaleeritavamaid Reacti rakendusi. Pidage meeles, et valige oma konkreetsetele vajadustele vastav muster ja kaaluge hoolikalt oma kontekstikasutuse mĂ”ju jĂ”udlusele.
Nagu React areneb, arenevad ka Context API-d ĂŒmbritsevad parimad praktikad. Uute tehnikate ja teekidega kursis olemine tagab, et olete valmis toime tulema kaasaegse veebiarenduse olekuhalduse vĂ€ljakutsetega. Kaaluge esilekerkivate mustrite, nĂ€iteks konteksti kasutamine signaalidega, uurimist veelgi peeneteralisema reaktiivsuse saavutamiseks.