React Context передает данные между различными уровнями вашего приложения без детализации реквизитов. Он предоставляет способ передачи данных через дерево компонентов.

Опора Сверление

Чтобы лучше понять контекст реакции, мы должны сначала понять проблему, которую он решает. Детализация свойств происходит, когда мы продолжаем бесконечно передавать данные дочерним компонентам, даже если некоторые из этих компонентов не используют их; они получают его, чтобы передать его своим детям.

Возьмем типичный пример, приложение-счетчик.

export default function App() {
  const [counter, setCounter] = useState(0);

  return (
    <div>
      <AddOneButton setCounter={setCounter} />
      <Counter counter={counter} />
    </div>
  );
}

У него есть два дочерних компонента: один для увеличения счетчика, а другой для отображения текущего счетчика.

const AddOneButton = ({setCounter}) => {
  return (
    <button onClick={() => setCounter((value) => value + 1)}>Add One</button>
  )
}

const Counter = ({counter}) => {
  return (
    <p>Count: {counter}</p>
  )
}

Теперь предположим, что у нас есть новый компонент с именем Container, который обертывает AddOneButton.

const Container = ({setCounter}) => {
  return <AddOneButton setCounter={setCounter} />
}

И теперь внутри App мы визуализируем новый компонент Container.

<Container setCounter={setCounter} />

Теперь ясно, что Container остро не нужна функцияsetCounter, мы передали ее только потому, что она нужна AddOneButton, и это то, что известно как 'Prop Drilling', вот почему нам нужен Context Api.

ПРИМЕЧАНИЕ

В этом случае мы можем смягчить использование сверления реквизита, сделав компонент Container общим компонентом и передав AddOneButton в качестве дочернего.

const Container = ({children}) => {
  return (
    {children}
  )
}

export default function App() {
  const [counter, setCounter] = useState(0);

  return (
    <div>
      <Container>
        <AddOneButton setCounter={setCounter} />
      </Container>

      <Counter counter={counter} />
    </div>
  );
}

Контекст с useState

теперь, когда мы разобрались с просверливанием пропеллеров, мы можем начать использовать Context Api, но перед этим нам нужно решить, каким образом мы будем реализовывать Context; в этом разделе в основном есть два способа либо с useState, либо с useReducer, мы сделаем это с помощью useState, начнем с импорта необходимых модулей.

import {createContext, useContext} from "react";

Затем мы создаем Context и его провайдера.

export const CounterContext = createContext(null);

export const CounterContextProvider = ({ children }) => (
  <CounterContext.Provider value={useState(0)}>
    {children}
  </CounterContext.Provider>
);

функция createContext инициализирует контекст, поставщик будет обертывать наше приложение (только компоненты внутри поставщика будут иметь доступ к данным), и, поскольку мы используем контекстный API, нам не нужно ничего передавать компонентам Container или Counter.

export default function App() {
  return (
    <CounterContextProvider>
      <Container />
      <Counter />
    </CounterContextProvider>
  );
}

Теперь для доступа к данным мы будем использовать хук useContext.

const Container = () => {
  return <AddOneButton />;
};

const AddOneButton = () => {
  const [, setCounter] = useContext(CounterContext);
  return (
    <button onClick={() => setCounter((value) => value + 1)}>Add One</button>
  );
};

const Counter = () => {
  const [counter] = useContext(CounterContext);
  return <p>Count: {counter}</p>;
};

Контекст с useReducer

Примечание. Рекомендуется использовать useReducer, если наше состояние более сложное.

Начнем с импорта необходимых модулей.

import { createContext, useContext, useReducer } from "react";

Теперь давайте создадим редьюсер и подключим его к нашим контекстным данным.

const reducer = (state, action) => {
  switch(action.type) {
    case "increment":
      return state + 1;
    default:
      return state
  }
}

export const CounterContext = createContext(null);

export const CounterContextProvider = ({ children }) => (
  <CounterContext.Provider value={useReducer(reducer, 0)}>
    {children}
  </CounterContext.Provider>
);

Хук возвращает кортеж, первый элемент — это значение, а второй — функция отправки.

const Container = () => {
  return <AddOneButton />;
};

const AddOneButton = () => {
  const [, dispatch] = useContext(CounterContext);
  return (
    <button onClick={() => dispatch({type: "increment"})}>Add One</button>
  );
};

const Counter = () => {
  const [counter] = useContext(CounterContext);
  return <p>Count: {counter}</p>;
};

И в основном так работает контекстный API.