Eesti

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 ( ); };
// 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:

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 (
setText(e.target.value)} />
); } 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:

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 (
setText(e.target.value)} />
); } 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

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.