O'zbek

React Context API uchun ilg'or patternlarni o'rganing, jumladan, tarkibiy komponentlar, dinamik kontekstlar va murakkab holatni boshqarish uchun optimallashtirilgan unumdorlik usullari.

Holatni Boshqarish uchun Ilg'or React Context API Patternlari

React Context API ilovangiz bo'ylab prop drilling'siz holatni ulashish uchun kuchli mexanizmni taqdim etadi. Asosiy foydalanish sodda bo'lsa-da, uning to'liq imkoniyatlaridan foydalanish murakkab holatni boshqarish stsenariylarini hal qila oladigan ilg'or patternlarni tushunishni talab qiladi. Ushbu maqolada ushbu patternlarning bir nechtasi o'rganilib, React dasturlashingizni yuqori darajaga ko'tarish uchun amaliy misollar va foydali maslahatlar taqdim etiladi.

Asosiy Context API'ning Cheklovlarini Tushunish

Ilg'or patternlarga o'tishdan oldin, asosiy Context API'ning cheklovlarini tan olish muhim. U oddiy, global miqyosda mavjud bo'lgan holat uchun mos bo'lsa-da, tez-tez o'zgaruvchan holatga ega murakkab ilovalar uchun noqulay va samarasiz bo'lib qolishi mumkin. Kontekstni ishlatadigan har bir komponent, kontekst qiymati o'zgarganda qayta render qilinadi, hatto komponent holatning yangilangan ma'lum bir qismiga bog'liq bo'lmasa ham. Bu unumdorlikda muammolarga olib kelishi mumkin.

Pattern 1: Kontekstli Tarkibiy Komponentlar

Tarkibiy Komponent patterni kontekst orqali holat va mantiqni yashirincha ulashadigan bir qator bog'liq komponentlarni yaratish orqali Context API'ni kuchaytiradi. Bu pattern qayta foydalanish imkoniyatini oshiradi va foydalanuvchilar uchun API'ni soddalashtiradi. Bu murakkab mantiqni oddiy amalga oshirish bilan kapsulalash imkonini beradi.

Misol: Tab (Varaqa) Komponenti

Buni Tab komponenti misolida ko'rib chiqamiz. Proplarni bir necha qatlam orqali uzatish o'rniga, Tab komponentlari umumiy kontekst orqali yashirincha aloqa qiladi.

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

Afzalliklari:

Pattern 2: Dinamik Kontekstlar

Ba'zi stsenariylarda sizga komponentlar daraxtidagi komponentning o'rniga yoki boshqa dinamik omillarga qarab turli kontekst qiymatlari kerak bo'lishi mumkin. Dinamik kontekstlar ma'lum shartlarga qarab o'zgaradigan kontekst qiymatlarini yaratish va taqdim etish imkonini beradi.

Misol: Dinamik Kontekstlar bilan Mavzulashtirish

Foydalanuvchi afzalliklari yoki ilovaning ular turgan qismiga qarab turli mavzularni taqdim etishni xohlagan mavzulashtirish tizimini ko'rib chiqing. Biz yorug' va qorong'i mavzu bilan soddalashtirilgan misol keltirishimiz mumkin.

// 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);
};
// Usage
import { useTheme, ThemeProvider } from './ThemeContext';

function MyComponent() {
  const { theme, toggleTheme } = useTheme();

  return (
    

This is a themed component.

); } function App() { return ( ); } export default App;

Ushbu misolda ThemeProvider isDarkTheme holatiga qarab mavzuni dinamik ravishda aniqlaydi. useTheme hook'idan foydalanadigan komponentlar mavzu o'zgarganda avtomatik ravishda qayta render qilinadi.

Pattern 3: Murakkab Holat uchun useReducer bilan Kontekst

Murakkab holat mantiqini boshqarish uchun Context API'ni useReducer bilan birlashtirish ajoyib yondashuvdir. useReducer holatni amallar (actions) asosida yangilashning tuzilmali usulini taqdim etadi va Context API ushbu holatni va dispatch funksiyasini ilovangiz bo'ylab ulashish imkonini beradi.

Misol: Oddiy Vazifalar Ro'yxati (Todo List)

// 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;
};
// Usage
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;

Bu pattern holatni boshqarish mantiqini reducer ichida markazlashtiradi, bu esa uni tushunish va sinovdan o'tkazishni osonlashtiradi. Komponentlar holatni to'g'ridan-to'g'ri boshqarmasdan uni yangilash uchun amallarni (actions) yuborishi mumkin.

Pattern 4: `useMemo` va `useCallback` bilan Kontekst Yangilanishlarini Optimallashtirish

Yuqorida aytib o'tilganidek, Context API bilan bog'liq asosiy unumdorlik muammosi keraksiz qayta renderlardir. useMemo va useCallback'dan foydalanish, faqat kontekst qiymatining kerakli qismlari yangilanishini va funksiya havolalari barqaror qolishini ta'minlash orqali bu qayta renderlarning oldini oladi.

Misol: Mavzu Kontekstini Optimallashtirish

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

Tushuntirish:

useCallback'siz, toggleTheme funksiyasi ThemeProvider'ning har bir renderida qayta yaratiladi, bu esa value o'zgarishiga va hatto mavzuning o'zi o'zgarmagan bo'lsa ham, barcha foydalanuvchi komponentlarda qayta renderlarni keltirib chiqaradi. useMemo yangi value faqat uning bog'liqliklari (theme yoki toggleTheme) o'zgarganda yaratilishini ta'minlaydi.

Pattern 5: Kontekst Selektorlari

Kontekst selektorlari komponentlarga faqat kontekst qiymatining ma'lum qismlariga obuna bo'lish imkonini beradi. Bu kontekstning boshqa qismlari o'zgarganda keraksiz qayta renderlarning oldini oladi. Bunga erishish uchun `use-context-selector` kabi kutubxonalar yoki maxsus yechimlardan foydalanish mumkin.

Maxsus Kontekst Selektoridan Foydalanish Misoli

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

    // You would typically subscribe to context changes here. Since this is a simplified
    // example, we'll just call subscription immediately to initialize.
    subscription();

    return () => {
      didUnmount = true;
      // Unsubscribe from context changes here, if applicable.
    };
  }, [value]); // Re-run effect whenever the context value changes

  return selected;
}

export default useCustomContextSelector;
// ThemeContext.js (Simplified for brevity)
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;
// Usage
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;

Ushbu misolda, BackgroundComponent faqat mavzuning background xususiyati o'zgarganda qayta render qilinadi va ColorComponent faqat color xususiyati o'zgarganda qayta render qilinadi. Bu butun kontekst qiymati o'zgarganda keraksiz qayta renderlardan qochish imkonini beradi.

Pattern 6: Amallarni Holatdan Ajratish

Kattaroq ilovalar uchun kontekst qiymatini ikki xil kontekstga ajratishni o'ylab ko'ring: biri holat uchun va ikkinchisi amallar (dispatch funksiyalari) uchun. Bu kodning tuzilishini va sinovdan o'tkazilishini yaxshilashi mumkin.

Misol: Alohida Holat va Amal Kontekstlariga ega Vazifalar Ro'yxati

// 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;
  }
};
// Usage
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;

Bu ajratish komponentlarga faqat o'zlariga kerakli kontekstga obuna bo'lishga imkon beradi, bu esa keraksiz qayta renderlarni kamaytiradi. Shuningdek, reducer va har bir komponentni alohida unit-testdan o'tkazishni osonlashtiradi. Bundan tashqari, provayderlarning o'rash tartibi muhim. ActionProvider StateProvider'ni o'rashi kerak.

Ilg'or Amaliyotlar va Tavsiyalar

Xulosa

React Context API holatni boshqarish uchun ko'p qirrali vositadir. Ushbu ilg'or patternlarni tushunish va qo'llash orqali siz murakkab holatni samarali boshqarishingiz, unumdorlikni optimallashtirishingiz va yanada qo'llab-quvvatlanadigan va kengaytiriladigan React ilovalarini yaratishingiz mumkin. O'z ehtiyojlaringiz uchun to'g'ri patternni tanlashni va kontekstdan foydalanishning unumdorlikka ta'sirini diqqat bilan ko'rib chiqishni unutmang.

React rivojlanar ekan, Context API bilan bog'liq ilg'or amaliyotlar ham o'zgarib boradi. Yangi usullar va kutubxonalar haqida xabardor bo'lish sizni zamonaviy veb-ishlab chiqishning holatni boshqarish muammolarini hal qilishga tayyorlaydi. Yanada nozikroq reaktivlik uchun signallar bilan kontekstdan foydalanish kabi paydo bo'layotgan patternlarni o'rganib ko'ring.