Как использовать ключи React, чтобы избежать конфликта компонентов

16
компьютеры и технологии 28.webp.webp

Последнее обновление 25.08.2023 — Василий Иванов

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

Особая ошибка возникает, когда вы условно визуализируете один и тот же компонент с разными свойствами. Подробно изучите эту ошибку и узнайте, как использовать ключи React для ее решения.

Компоненты React не всегда независимы

Его простой синтаксис — одна из основных причин, по которой вам следует изучить React. Но, несмотря на множество преимуществ, фреймворк не лишен ошибок.

Ошибка, о которой вы узнаете здесь, возникает, когда вы условно визуализируете один и тот же компонент, но передаете ему разные реквизиты.

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

По теме:  Как использовать телефон Android в качестве веб-камеры в Windows 11

Для демонстрации возьмем этот пример. Во-первых, у вас есть следующий компонент Counter:

 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. Затем он отображает имя в

. Он также возвращает две кнопки: одну для уменьшения счетчика в состоянии, а другую для его увеличения.

Имейте в виду, что в приведенном выше коде нет ничего плохого. Ошибка возникает из следующего блока кода (компонент приложения), который использует счетчик:

 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 не знает, что счетчик «Кингсли» отличается от счетчика «Салли». Единственное отличие заключается в имени, но, к сожалению, React не использует его для различения элементов.

Обойти эту проблему можно двумя способами. Первый — изменить DOM и сделать два дерева разными. Для этого необходимо, чтобы вы понимали, что такое DOM. Например, вы можете обернуть первый счетчик внутри элемента

, а второй — внутри элемента

:

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

Теперь каждый раз, когда вы переключаетесь между элементами

для Кингсли и Салли, вы автоматически меняете ключевой атрибут ввода между «Кингсли» и «Салли». Это заставит React полностью перерисовывать элемент ввода при каждом нажатии кнопки.

Дополнительные советы по оптимизации приложений React

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

Самое приятное то, что вы можете применять большинство этих методов оптимизации и с приложениями React Native.

Предыдущая статьяLeft to Survive
Следующая статьяRemnant 2 – Как получить бремя Божественного Кольца
Василий Иванов
Василий - внештатный автор сайта, специализирующийся на технологических новостях. Ранее он работал в качестве автора заявок и корректора. Василий активно пишет и рассказывает о потребительских технологиях. Его главная страсть - компьютеры, но он с удовольствием расскажет вам обо всем, что имеет процессор.