Проп key для пересоздания компонента в ReactJS
2 года назад·2 мин. на чтение
Знали ли вы, что проп key может быть полезен не только при рендеринге списка компонентов. Проп key можно использовать и для того чтобы сбросить состояние одного компонента.
Что такое проп key
в ReactJS?
Это специальный проп, который может быть добавлен к любому компоненту. Он помогает механизму reconciliation (согласование), упрощая сравнение компонентов. Типичный сценарий использования key
- добавление его в компоненты списка. Он нужен для того чтобы React понимал, какой компонент списка был добавлен, удален или изменен.
const notes = [ { id: 1, title: 'React hooks', }, { id: 2, title: 'JSX', }, { id: 3, title: 'Redux', }, ]; const NotesList = ({ notes, onClick }) => { return ( <div className="notes-list"> {notes.map((note) => ( <p className="notes-list__item" key={note.id} onClick={() => onClick(note)} > {note.title} </p> ))} </div> ); };
Проп key
работает и вне списков
Проп key
может быть добавлен к абсолютно любому компоненту для того, чтобы сбросить нежелательное состояние этого компонента.
Например, в списке заметок есть поле для ввода текста. Если просто добавить это поле и ввести в него текст, то при выборе новой заметки слева - текст будет сохраняться. И, предположим, при выборе заметки мы хотим очистить это поле.
function App() { const [activeNote, setActiveNote] = useState(); const handleClick = (note) => { setActiveNote(note); }; return ( <div className="notes-container"> <NotesList notes={notes} onClick={handleClick} /> <Note title={activeNote?.title} /> </div> ); }
Это можно сделать, например, добавив пропconst Note = ({ title }) => { const [text, setText] = useState(); const handleChange = (event) => { setText(event.target.value); }; return ( <div className="note"> <p>{title}</p> <textarea className="note-textarea" value={text} onChange={handleChange} /> </div> ); };
text
в компонент Note
. И далее очищать его при изменении состояния activeNote
. Но изменение компонентов может быть невозможным, если мы используем компоненты из third-party библиотеки.
Сброс состояние экземпляра компонента
Пропkey
помогает React идентифицировать компонент. Его также можно использовать, чтобы сообщить React, что идентификатор компонента изменился и это вызовет полное повторное создание этого компонента. Добавим key={activeNote?.id}
к компоненту <Note />
.
Теперь, при изменении// ... return ( <div className="notes-container"> <NotesList notes={notes} onClick={handleClick} /> <Note title={activeNote?.title} key={activeNote?.id} /> </div> ); }
key
React пересоздаст компонент <Note />
.
Влияние на производительность
Хотя это хороший прием, который уменьшает количество кода, важно иметь ввиду, что этот подход заставляет React пересоздавать весь экземпляр компонента. В примере выше большая часть компонента<Note />
будет перерисована в любом случае при изменении activeNote
. Поэтому в этом случае это достаточно хорошее решение.
В реальных приложениях нужно ограничивать добавление key
к одиночным компонентам вне списков, а также избегать добавления key
на компоненты верхнего уровня. Это может стать причиной проблем с производительностью, которые трудно обнаружить.Как работать с контекстом в React?
2 года назад·5 мин. на чтение
Контекст позволяет родительскому компоненту сделать некоторую информацию доступной для любого компонента в дереве под ним — независимо от того, насколько он глубок — без явной передачи ее через пропсы.
Обычно вы передаете информацию от родительского компонента дочернему компоненту через пропс. Но передача пропса может стать многословной и неудобной, если вам придется передавать их через множество компонентов посередине или если многим компонентам вашего приложения требуется одна и та же информация.
Допустим, вы хотите, чтобы несколько заголовков в одном разделе всегда имели одинаковый размер:
Проблема с передачей пропсов в React
Передача пропсов - отличный способ явно передать данные через UI дерево компонентам, которые его используют. Но передача пропса может стать многословным и неудобным, когда вам нужно передать какой-то проп глубоко через дерево, или если многим компонентам нужен один и тот же проп. Ближайший общий предок может быть далек от компонентов, которые нуждаются в данных, и поднятие состояния вверх может привести к ситуации, иногда называемой “prop drilling”. Было бы здорово, просто «телепортировать» данные к компонентам дерева, которые в них нуждаются.Контекст: альтернатива передаче пропсов
Контекст позволяет родительскому компоненту предоставлять данные всему дереву под ним. Есть много применений для контекста. Вот один из примеров. Рассмотрим компонентHeading
, который принимает level
для своего размера:
// App.js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return ( <Section> <Heading level={1}>Title</Heading> <Heading level={2}>Heading</Heading> <Heading level={3}>Sub-heading</Heading> <Heading level={4}>Sub-sub-heading</Heading> <Heading level={5}>Sub-sub-sub-heading</Heading> <Heading level={6}>Sub-sub-sub-sub-heading</Heading> </Section> ); }
// Section.js export default function Section({ children }) { return ( <section className="section"> {children} </section> ); }
// Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return <h1>{children}</h1>; case 2: return <h2>{children}</h2>; case 3: return <h3>{children}</h3>; case 4: return <h4>{children}</h4>; case 5: return <h5>{children}</h5>; case 6: return <h6>{children}</h6>; default: throw Error('Unknown level: ' + level); } }
В настоящее время вы передаете проп// App.js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return ( <Section> <Heading level={1}>Title</Heading> <Section> <Heading level={2}>Heading</Heading> <Heading level={2}>Heading</Heading> <Heading level={2}>Heading</Heading> <Section> <Heading level={3}>Sub-heading</Heading> <Heading level={3}>Sub-heading</Heading> <Heading level={3}>Sub-heading</Heading> <Section> <Heading level={4}>Sub-sub-heading</Heading> <Heading level={4}>Sub-sub-heading</Heading> <Heading level={4}>Sub-sub-heading</Heading> </Section> </Section> </Section> </Section> ); }
level
каждому <Heading>
отдельно:
Было бы неплохо, если бы вы могли передать проп<Section> <Heading level={3}>About</Heading> <Heading level={3}>Photos</Heading> <Heading level={3}>Videos</Heading> </Section>
level
в <Section>
и удалить его из <Heading>
. Таким образом, вы можете добиться того, чтобы все заголовки в одном разделе имели одинаковый размер:
Но как<Section level={3}> <Heading>About</Heading> <Heading>Photos</Heading> <Heading>Videos</Heading> </Section>
<Heading>
может узнать уровень своего ближайшего <Section>
? Для этого дочернему компоненту потребуется какой-то способ «попросить» данные откуда-то сверху на дереве.
Вы не можете сделать это только с пропсами. Именно здесь в игру вступает контекст. Вы можете сделать это в три шага:
- Создайте контекст. (Вы можете назвать его
LevelContext
, так как он предназначен для уровня заголовка) - Используйте этот контекст из компонента, которому требуются данные. (
Heading
будет использоватьLevelContext
) - Укажите этот контекст из компонента, задающего данные. (
Section
будет содержатьLevelContext
.)
Шаг 1: Создайте контекст
Во-первых, вам нужно создать контекст. Вам нужно будет экспортировать его из файла, чтобы ваши компоненты могли использовать его:Единственным аргументом// LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(1);
createContext
является значение по умолчанию. Здесь 1
относится к самому большому уровню заголовка, но вы можете передать любое значение (даже объект). Вы увидите значение значения по умолчанию на следующем шаге.
Шаг 2: Используйте контекст
Импортируйте хукuseContext
из React и импортируйте контекст, созданный на первом шаге:
Как вы помните компонент// Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js';
Heading
считывает level
из пропса:
Вместо этого удалите пропexport default function Heading({ level, children }) { // ... }
level
и прочитайте значение из только что импортированного контекста LevelContext
:
export default function Heading({ children }) { const level = useContext(LevelContext); // ... }
useContext
— это хук. Так же, как useState
и useReducer
, вы можете вызвать хук только на верхнем уровне компонента React. useContext
сообщает React, что компонент Heading
хочет прочитать LevelContext
.
Теперь, когда компонент Heading
не имеет пропса level
, вам больше не нужно передавать проп level
в Heading
в вашем JSX.
Обновите JSX так, чтобы его получал Section
:
Обратите внимание, что этот пример еще не совсем работает. Все заголовки имеют одинаковый размер, потому что хоть мы и вызвали контекст, но еще не предоставили его. React не знает, где его взять. Если контекст не указан, React будет использовать значение по умолчанию, указанное на предыдущем шаге. В этом примере в качестве аргумента<Section level={4}> <Heading>Sub-sub-heading</Heading> <Heading>Sub-sub-heading</Heading> <Heading>Sub-sub-heading</Heading> </Section>
createContext
мы передали 1
, поэтому useContext(LevelContext)
возвращает 1
, установив для всех этих заголовков <h1>
. Давайте исправим эту проблему, предоставив каждому Section
свой собственный контекст.
Шаг 3: Предоставьте контекст
КомпонентSection
в настоящее время рендерит дочерние компоненты. Оберните их поставщиком контекста (context provider), чтобы предоставить им LevelContext
:
Это говорит React: «Если какой-либо компонент внутри этогоimport { LevelContext } from './LevelContext.js'; export default function Section({ level, children }) { return ( <section className="section"> <LevelContext.Provider value={level}> {children} </LevelContext.Provider> </section> ); }
<Section>
просит LevelContext
, дайте им этот level
». Компонент будет использовать значение ближайшего <LevelContext.Provider>
в UI дереве над ним.
Этот код будет работать так же как и в самой первой реализации. Но вам не нужно было передавать проп level
каждому компоненту Heading
. Вместо этого он «выясняет» свой уровень заголовка, спрашивая ближайший Section
выше:
- Вы передаете проп
level
в<Section>
. Section
оборачивает свои дочерние элементы в<LevelContext.Provider value={level}>
.Heading
запрашивает ближайшее значениеLevelContext
выше сuseContext(LevelContext)
.
Типичные варианты использования контекста
- Темы: Если ваше приложение позволяет пользователю изменять его внешний вид (например, в темном режиме), вы можете поместить поставщика контекста в верхнюю часть приложения и использовать этот контекст в компонентах, которые должны настроить свой визуальный вид.
- Текущий пользователь приложения: Многим компонентам может потребоваться знать текущего вошедшего в систему пользователя. Помещение его в контекст позволяет удобно читать его в любом месте дерева. Некоторые приложения также позволяют управлять несколькими учетными записями одновременно (например, оставлять комментарий как другой пользователь). В этих случаях может быть удобно обернуть часть пользовательского интерфейса во вложенного поставщика с другим значением текущего счета.
- Маршрутизация: Большинство решений маршрутизации используют внутренний контекст для хранения текущего маршрута. Таким образом, каждая ссылка «знает», активна она или нет. Если вы создаете свой собственный маршрутизатор, вы тоже можете это сделать.
- Управление состоянием: По мере роста вашего приложения вы можете получить большое состояние ближе к верхней части вашего приложения. Многие отдаленные компоненты ниже могут захотеть изменить его. Обычно используется редьюсер вместе с контекстом для управления сложным состоянием и передачи его удаленным компонентам без особых хлопот.
Итоги
- Контекст позволяет компоненту предоставлять некоторую информацию всему дереву под ним.
-
Чтобы передать контекст:
- Создайте и экспортируйте его с
export const MyContext = createContext(defaultValue)
. - Передайте его в хук
useContext(MyContext)
чтобы прочитать его в любом дочернем компоненте, независимо от глубины. - Оберните дочерние комоненты в
<MyContext.Provider value={...}>
для представления контекста из родительского компонента.
- Создайте и экспортируйте его с
- Контекст проходит через любые компоненты посередине.
- Контекст позволяет писать компоненты, которые «адаптируются к окружающей среде».
-
Прежде чем использовать контекст, попробуйте передать проп или передать JSX как
children
.