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
,TabList
jaTabPanel
pärast. - Kaudne oleku jagamine: Komponendid pääsevad automaatselt juurde jagatud olekule ja uuendavad seda.
- Parem taaskasutatavus:
Tab
komponenti 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:
useCallback
memoiseeribtoggleTheme
funktsiooni. See tagab, et funktsiooniviide muutub ainult siis, kuiisDarkTheme
muutub, vältides tarbetuid uuesti renderdamisi komponentides, mis sõltuvad ainulttoggleTheme
funktsioonist.useMemo
memoiseerib konteksti väärtuse. See tagab, et konteksti väärtus muutub ainult siis, kui muutub kastheme
võitoggleTheme
funktsioon, 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.