Документы React отлично подходят для того, чтобы всегда показывать вам самый простой вариант использования любой конкретной функции React. Они показывают вам функции в их простейшей реализации и изолированно. Проблема в том, что это слишком упрощено. Вы попадаете в настоящую базу кода или начинаете создавать что-то даже средней сложности, и вы быстро попадаете в море блогов, книг, руководств, сообщений о переполнении стека и т. Д., Чтобы найти информацию, которая вам неизбежно понадобится. Это особенно верно, когда речь идет о шаблонах, которые вам понадобятся. Я полагаю, некоторые могут сказать, что паттерны — это абстракции помимо React, и нужно просто знать их сами по себе (что верно), но если у вас нет степени CS, вы можете и не знать. Все это означает, что если вы хотите создавать прототипы с помощью React и создавать что-то средней сложности (как я), вы быстро столкнетесь с проблемой чрезмерного детализации Prop Drill, и вам нужно будет использовать другой шаблон, чтобы обойти ее.

Что такое опорное бурение?

Это просто акт передачи вещей через props, таких как части state, и функции, которые изменяют эти state через компоненты, чтобы доставить их туда, куда им нужно. Например, в state вы можете хранить массив значений, где каждое значение является «задачей» и должно быть передано в компонент <List>, а затем в компонент <Item> после этого. Точно так же, возможно, у вас есть компонент <Button>, который позволяет пользователю пометить задачу как выполненную, которая является потомком <Button>, теперь вам нужно передать какую-то функцию handleEvent на несколько уровней глубже, чтобы иметь возможность запускать ее через onClick, который фактически сидит на кнопке. Можно с уверенностью сказать, что это быстро становится неустойчивым.

По какой-то причине в документах React контекст указан в разделе «Расширенные руководства» вместо «Основные концепции». Я думаю, что это своего рода медвежья услуга для людей, которые только начинают работать с React, потому что (1) вам нужно сразу же знать, как его использовать, (2) его не сложнее понять, чем любую другую концепцию React, и (3) если вы не Если вы не знаете, как им пользоваться, работодатели тут же отвергают вас как человека, который не знает, что делает. Итак, давайте посмотрим на пример сверления пропеллеров, а затем на то, как получить точно такую ​​же функциональность с помощью Context.

Все, что у нас есть, — это простейшая реализация списка задач. Однако он сохраняет состояние. Документы React ясно объясняют: «Часто несколько компонентов должны отражать одни и те же изменяющиеся данные. Мы рекомендуем поднимать общее состояние до их ближайшего общего предка». Мы делаем это, сохраняя состояние в <App />.

Теперь вы можете видеть, что у нас есть state и функция handleEvent , которая вызывается при определенных событиях (onChange, onClick) и изменяет state. Как сказано в документации React, у нас есть несколько компонентов, которые либо должны отражать состояние, либо запускать обработчик, изменяющий состояние. Но поскольку состояние и обработчик, который его изменяет, находятся в <App />, как эти данные и/или функция для управления ими попадают куда-нибудь? Опорное бурение.

Вы видели в Gist выше реквизиты, переданные в <DashBoard />.

<DashBoard
  items={state.items}
  handleEvent={handleEvent}
  inputValue={state.todo}
/>

Затем в <DashBoard />

const DashBoard = ({ items, handleEvent, inputValue }) => {
return (
  <>
    <Input handleEvent={handleEvent} inputValue={inputValue} />
    <Button handleEvent={handleEvent}>add task</Button>
    <List items={items} />
  </>
);
}
export default DashBoard;

… вы должны деструктурировать пропсы в верхней части компонента, а затем передать их (просверлить) в <Input />, <Button /> и <List />.

Теперь и для <Input />, и для <Button /> это конец пути, как видите.

const Input = ({ handleEvent, inputValue }) => {
return <input onChange={handleEvent} name="todo" type='text' value={inputValue} placeholder="type todo here..."/>
}
export default Input;
const Button = ({ handleEvent, children }) => {
  return <button onClick={handleEvent}>{children}</button>
}
export default Button;

<List />, с другой стороны, отображает массив items для построения компонентов <Item />.

const List = ({ items }) => {
  const itemsForList = items.map((item, index) => <Item key={index}        text={item.title} />)
return (
  <div>
    {itemsForList}
  </div>
  );
}
export default List;

Итак, у вас есть часть данных, которая хранится в <App />, должна перейти от <App /> к <DashBoard />, к <List /> и, наконец, к <Item />, чтобы попасть туда, куда она действительно должна идти. То же самое и с обработчиком событий, чтобы сработать в onClick, который находится на фактической кнопке, он должен перейти от <App /> к <DashBoard /> и <Button />. Вы можете начать видеть, что это уже становится немного громоздким, и это в безумно простом маленьком приложении, состоящем из шести компонентов. Время идти в другом направлении — контекст.

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

Во-первых, в документах React говорится: «Контекст предоставляет способ передачи данных через дерево компонентов без необходимости вручную передавать реквизиты на каждом уровне». Отлично, это то, чего мы хотим избежать — передачи их вниз на каждом уровне.

Вы можете видеть вверху, что мы импортируем createContext и создаем объект контекста с именем ItemsContext, который «Создает объект контекста. Когда React визуализирует компонент, который подписывается на этот объект Context, он считывает текущее значение контекста из ближайшего соответствующего Provider над ним в дереве». Вы также можете видеть, что state и handleEvent абсолютно одинаковы. Двигаясь вниз, мы просто возвращаем наш ItemsContext.Provider… что это? Что ж, снова процитируем здесь… «Каждый объект Context поставляется с компонентом Provider React, который позволяет потребляющим компонентам подписываться на изменения контекста». И он «принимает свойство value для передачи потребляющим компонентам, которые являются потомками этого провайдера». Итак, в основном мы создаем объект Context, затем заполняем этот объект тем, что хотим, а затем можем передавать его куда угодно.

Помните, что мы должны передать дочерние элементы через ItemsProvider, потому что мы собираемся разместить его выше в дереве компонентов React, и компоненты, которым он нужен, будут его дочерними элементами. Итак, компоненты, которые нуждаются в этом, как они его получают? Поскольку мы используем все функциональные компоненты, мы можем использовать React Hooks.

Да, кстати, проверьте <App /> сейчас — ни состояния, ни обработчиков — потому что нечего передавать.

Хорошо, вернемся к тому, как мы получаем контекст с помощью хуков. Давайте используем <Input /> в качестве примера, потому что ему нужен и обработчик, и state.

Просто импортируйте хук useContext и нужный вам контекст, возьмите из него то, что вам нужно, и используйте его. Вот и все. О, и еще один важный момент: он доступен только потому, что мы предоставили его выше в дереве, например:

Вот и все.

Если вы хотите посмотреть на код, управляемый Prop Drilling, рядом с кодом, управляемым контекстом, ниже приведены два репозитория. Развлекайся. Оставляйте комментарии и рассказывайте, что можно улучшить/что я сделал не так. Спасибо.

Версия для бурения опор:



Контекстная версия: