Как использовать ключи React, чтобы избежать конфликта компонентов
Как вы можете убедить React, что два варианта использования компонента нуждаются в своем индивидуальном состоянии? Конечно, с ключами!
Подход React может быть довольно сложным, и вы можете столкнуться с неожиданным поведением или даже незначительными ошибками. Избавиться от таких ошибок может быть довольно сложно, если вы не знаете их причину.
Особая ошибка возникает, когда вы условно визуализируете один и тот же компонент с разными свойствами. Подробно изучите эту ошибку и узнайте, как использовать ключи React для ее решения.
Компоненты React не всегда независимы
Его простой синтаксис — одна из основных причин, по которой вам следует изучить React. Но, несмотря на множество преимуществ, фреймворк не лишен ошибок.
Ошибка, о которой вы узнаете здесь, возникает, когда вы условно визуализируете один и тот же компонент, но передаете ему разные реквизиты.
В подобных случаях React будет считать, что эти два компонента одинаковы, поэтому он не будет рендерить второй компонент. В результате любое состояние, определенное вами в первом компоненте, будет сохраняться между рендерингами.
Для демонстрации возьмем этот пример. Во-первых, у вас есть следующий компонент Счетчик:
import { useState, useEffect } from "react"
export function Counter({name}) {
const [count, setCount] = useState(0)
return(
<div>
<div>{name}</div>
<button onClick={() => setCount(c => c - 1)}> - </button>
<br />
<button onClick={() => setCount(c => c + 1)}> + </button>
</div>
)
}
Этот компонент Counter принимает имя от родительского элемента посредством деструктуризации объекта, что является способом использования реквизитов в React. Затем он отображает имя в <div>. Он также возвращает две кнопки: одну для уменьшения счетчика в состоянии, а другую для его увеличения.
Имейте в виду, что в приведенном выше коде нет ничего плохого. Ошибка возникает из следующего блока кода (компонент приложения), который использует счетчик:
import { useState } from "react"
import { Counter } from "./Counter"
export default function App() {
const [isKingsley, setIsKingsley] = useState(true)
return(
<div>
{ isKingsley ? <Counter name="Kingsley" /> : <Counter name="Sally" /> }
<br />
<button onClick={() => setIsKingsley(k => !k)}> Swap </button>
</div>
)
}
По умолчанию приведенный выше код отображает счетчик с именем Kingsley. Если вы увеличите счетчик до пяти и нажмете кнопку Поменять, отобразится второй счетчик с именем Салли.
Но проблема в том, что счетчик не сбрасывается в нулевое состояние по умолчанию после того, как вы их поменяли местами.
Эта ошибка возникает из-за того, что оба состояния отображают одни и те же элементы в одном и том же порядке. React не знает, что счетчик «Кингсли» отличается от счетчика «Салли». Единственное отличие заключается в свойстве name, но, к сожалению, React не использует его для различения элементов.
Обойти эту проблему можно двумя способами. Первый — изменить DOM и сделать два дерева разными. Для этого необходимо, чтобы вы понимали, что такое DOM. Например, вы можете поместить первый счетчик в элемент <div>, а второй – в элемент :
import { useState } from "react"
import { Counter } from "./Counter"
export default function App() {
const [isKingsley, setIsKingsley] = useState(true)
return (
<div>
{ isKingsley ?
(<div>
<Counter name="Kingsley" />
</div>)
:
(<section>
<Counter name="Sally" />
</section>)
}
<br />
<button onClick={() => setIsKingsley(k => !k)}> Swap </button>
</div>
)
}
Если вы увеличите счетчик «Кингсли» и нажмете Поменять, состояние сбросится на 0. Опять же, это происходит потому, что структура двух деревьев DOM различна.
Если переменная isKingsley имеет значение true, структура будет иметь вид div > div > Counter (div, содержащий div, содержащий Counter). Когда вы меняете состояние счетчика с помощью кнопки, структура становится div > раздел > Счетчик. Из-за этого несоответствия React автоматически отобразит новый счетчик с состоянием сброса.
Возможно, вам не всегда захочется изменять структуру разметки таким образом. Второй способ решения этой ошибки позволяет избежать необходимости использования другой разметки.
Использование ключей для рендеринга нового компонента
Ключи позволяют React различать элементы во время процесса рендеринга. Итак, если у вас есть два абсолютно одинаковых элемента, и вы хотите сообщить React, что один из них отличается от другого, вам необходимо установить уникальный ключевой атрибут для каждого элемента.
Добавьте ключ к каждому счетчику, например:
import { useState } from "react"
import { Counter } from "./Counter"
export default function App() {
const [isKingsley, setIsKingsley] = useState(true)
return(
<div>
{ isKingsley ?
<Counter key="Kingsley" name="Kingsley" /> :
<Counter key="Sally" name="Sally" />
}
<br />
<button onClick={() => setIsKingsley(k => !k)}> Swap </button>
</div>
)
}
Теперь, когда вы увеличиваете счетчик «Кингсли» и нажимаете Поменять, React отображает новый счетчик и сбрасывает состояние на ноль.
Вам также следует использовать ключи при рендеринге массива элементов одного типа, поскольку React не будет знать разницы между каждым элементом.
export default function App() {
const names = ["Kingsley", "John", "Ahmed"]
return(
<div>
{ names.map((name, index) => {
return <Counter key={index} name={name} />
})}
</div>
)
}
Когда вы назначаете ключи, React свяжет отдельный счетчик с каждым элементом. Таким образом, он может отражать любые изменения, которые вы вносите в массив.
Еще один вариант использования расширенного ключа
Вы также можете использовать ключи, чтобы связать элемент с другим элементом. Например, вы можете захотеть связать элемент ввода с разными элементами в зависимости от значения переменной состояния.
Для демонстрации настройте компонент App:
import { useState } from "react"
export default function App() {
const [isKingsley, setIsKingsley] = useState(true)
return(
<div>
{ isKingsley ? <div>Kingsley's Score</div> : <div>Sally's score</div> }
<input key={ isKingsley? "Kingsley" : "Sally" } type="number"/>
<br />
<button onClick={() => setIsKingsley(k => !k)}> Swap </button>
</div>
)
}
Теперь каждый раз, когда вы переключаетесь между элементами <div> для Кингсли и Салли, вы автоматически меняете ключевой атрибут ввода между «Кингсли» и «Салли». Это заставит React полностью перерисовывать элемент ввода при каждом нажатии кнопки.
Дополнительные советы по оптимизации приложений React
Оптимизация кода является ключом к созданию приятного пользовательского опыта в вашем веб-приложении или мобильном приложении. Знание различных методов оптимизации может помочь вам получить максимальную отдачу от ваших приложений React.
Самое приятное то, что вы можете применять большинство этих методов оптимизации и с приложениями React Native.
Часто задаваемые вопросы
Вопрос: Что такое React Native?
React Native расширяет React, предоставляя основу для разработки кроссплатформенных приложений. Это может значительно сократить общее время разработки.
Вопрос: Должен ли я всегда обеспечивать 100% изоляцию своих компонентов?
Система компонентов React помогает инкапсулировать данные, но иногда вам придется ее обойти. Состояние подъема является одним из подходов, но оно может привести к винтовому бурению, имеющему недостатки.
Вопрос: Как я могу использовать реквизиты для создания гибкого компонента?
Вы можете следовать инструкциям по созданию образца компонента уведомлений React, который использует реквизиты для настройки.