Все ли вы знаете о React key?

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Привет, Хабр!


Я время от времени провожу собеседования, и когда вопрос касается React key, чаще всего я вижу недоумевающий взгляд, намекающий “Да, там и спрашивать вроде нечего?”. Если Вам кажется React key понятным и простым, тогда давайте проведем мини собеседование (данная статья является расшифровкой видео)


Задачка разминка


Постановка задачи


Представьте, что у нас есть массив из трех пользователей. Структура пользователя максимально примитивная, всего 2 поля, это id — уникальный идентификатор и второе поле name — собственно имя пользователя.


const users = [{
  id: 1,
  name: 'Alexander',
}, {
  id: 2,
  name: 'Dmitriy',
}, {
  id: 3,
  name: 'Anton',
}];

Попробуем отрисовать этих пользователей, для этого используем следующий код:


const Users = ({ users }) => {
  return users.map((user) => {
    return (
      <User
        key={user.id}
        name={user.name}
      />
    );
  }
}

Давайте рассмотрим, что из себя представляем компонент User.


class User extends PureComponent {
  componentDidMount() {
    console.log("DID MOUNT  ", this.props.name);
  }
  componentDidUpdate(prevProps) {
    console.log("DID UPDATE  ", prevProps.name, " -> ", this.props.name);
  }
  componentWillUnmount() {
    console.log("WILL UNMOUNT  ", this.props.name);
  }
  render() {
    return (
      <span>{this.props.name}</span>
    );
  }
}

Я написал этот компонент на классах, для лучшей наглядности жизненного цикла. И еще один момент на который хотелось бы обратить внимание это — PureComponent. Это значит, что компонент обновится, только в случае изменения свойств (props).


В итоге в браузере мы увидим примерно следующую картину:




Мы видим список из трех имен, а в консоли видим три записи, а именно DID MOUNT для каждого пользователя. Пока все логично и предсказуемо.


Интрига задачи и Вопрос


А теперь перейдем собственно к интриге задачи. Допустим у первого пользователя изменилось имя, у второго пользователя как бы это странно не звучало изменилось id, а у третьего ничего не изменилось.


const users = [{
  id: 1,
  name: 'Maxim', // 'Alexander',
}, {
  id: 4, // 2,
  name: 'Dmitriy',
}, {
  id: 3,
  name: 'Anton',
}];

Внимание вопрос!
Какие новые логи вы увидите в консоли?




Даем минутку подумать, можете поставить на паузу и попробовать ответить самостоятельно...








Осторожно Ответ!


Ну что, давайте сравнивать ответы. В браузере мы видим следующую картину:




На самой страничке мы видим, что Alexander изменился на Maxim, а Dmitriy и Anton, остались на первый взгляд без изменений. А в консоли мы видим следующие логи:


  1. WILL UNMOUNT Dmitriy
  2. DID UPDATE Alexander изменился на Maxim
  3. Dmitriy снова вернулся в строй.

Напишите в комментарии, правильно ли вы ответили? И если нет, в чем была ошибка


Разбор ответа


Давайте разберемся, почему мы получили именно такие результаты


В случае пользователя Anton, key и name до перерисовки, совпадают с key и name после перерисовки, а я напоминаю что сам компонент User это PureComponent. Соответственно никаких перерисовок компонента не произошло и следственно записей в консоли не появилось.




Пользователь Alexander изменил свойство name на Maxim, а при изменении props компонент перерисовывается и логично, что вызывается componentDidUpdate. Соответственно мы и увидели запись в консоли, о том что компонент обновился.




И самый непонятный пользователь Dmitriy, не смотря на то что компонент User это PureComponent и мы не меняли name, с компонентом все равно что-то происходило. А именно прошлый пользователь Dmitriy уничтожился и новый клон Dmitriy создался заново. О чем свидетельствуют записи в консоли WILL UNMOUN и DID MOUNT.




В этом и состоит суть React key. Если ДО обновлений, key существовал, а ПОСЛЕ обновлений key исчез, то не важно, чему был присвоен key, этот компонент в любом случае будет уничтожен. И наоборот, если ДО обновлений key не существовал, а ПОСЛЕ обновлений key появился, тогда этот компонент в любом случае будет создан с нуля. Хотя со стороны пользователя в данном примере вы не увидите никаких изменений, как был Dmitriy так он и остался, разница может быть видна только в более сложных компонентах, или при их большом количестве


Задачка посложнее


Идею я думаю вы уловили, но любую теорию нужно закрепить более сложными примерами!


Предыстория


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


Чтобы разобраться в этой проблеме, я бы для начала заглянул, что об этом говорит React документация:




А как именно негативно, рассмотрим в следующем примере.


Постановка задачи


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


const users = [{
  id: 1,
  name: 'Alexander',
}, {
  id: 2,
  name: 'Dmitriy',
}, {
  id: 3,
  name: 'Anton',
}, {
  id: 4,
  name: 'Artem',
}, {
  id: 5,
  name: 'Andrey',
}];

Для key будем использовать вместо idindex


const Users = ({ users }) => {
  return users.map((user) => {
    return (
      <User
        key={index}
        name={user.name}
      />
    );
  }
}

А компонент User остался без изменений.


В итоге в браузере мы увидим 5 пользователей и в консоли по одной записи DID MOUNT для каждого пользователя. Далее происходит следующее, второго пользователя Dmitriy удалили из списка.


const users = [{
  id: 1,
  name: 'Alexander',
}/*, {
  id: 2,
  name: 'Dmitriy',
}*/, {
  id: 3,
  name: 'Anton',
}, {
  id: 4,
  name: 'Artem',
}, {
  id: 5,
  name: 'Andrey',
}];

Вопрос


И тут возникает вопрос, какие логи появятся в консоли после обновлений?


Ставьте на паузу и подумайте над ответом.






Осторожно Ответ!


И так результат будет следующим




Первый лог и самый спорный это WILL UNMOUNT Andrey, спорный он потому что, мы удаляли Dmitriy, а удалился Andrey. Следующие 3 лога уже немного поясняют, то что произошло на самом деле. Мы видим DID UPDATE для трех пользователей.


Разбор ответа


Давайте еще раз разберемся, что именно произошло. Было 5 пользователей. И мы начинаем мыслить как пользователь, т.е. мы нажали кнопку удалить второго пользователя Dmitriy. Соответственно и ожидаем, что именно компонент второго пользователя будет удален, а остальные компоненты останутся без изменений.




А теперь давайте рассмотрим, как это видит React. До обновлений было 5 компонентов, каждому из них присвоили key. После обновлений стало 4 компонента. Каждому так же присвоен key. И react, с помощью одинакового key, соотносит компонент до рендера, с компонентом после рендера.




А теперь давайте посмотрим какие имена соответствуют этим ключам.




И тут начинаешь понимать, почему логи были именно такими. В компоненте, котором находился Dmitriy, теперь передали просто новый props name со значением Anton. Соответственно мы и увидели лог DID UPDATE. Тоже самое произошло и со следующими двумя компонентами, поэтому мы и увидели в консоли 3 раза DID UPDATE. А key равный 5, перестал существовать, собственно поэтому мы и увидели WILL UNMOUNT Andrey, а не WILL UNMOUNT Dmitriy.


Подведем итог задаче


Если бы мы использовали id, а не index в качестве значения для key. Все было бы совсем по другому, и у нас действительно удалился бы только один компонент который содержал пользователя Dmitriy, а остальные остались бы без изменений. Именно про такие негативные последствия и предупреждали нас в React документации. И удаление элемента как вы понимаете, это не единственный случай, когда у нас могут быть лишние рендеры, так же при drag and drop, добавлении нового элемента в середину списка и даже более специфические ситуации.


Домашка


Например представьте что у пользователей появились роли, обычный пользователь и администратор:


const users = [{
  id: 1,
  name: 'Alexander',
  role: 'user',
}, {
  id: 2,
  name: 'Dmitriy',
  role: 'admin',
}, {
  id: 3,
  name: 'Anton',
  role: 'user',
}, {
  id: 4,
  name: 'Artem',
  role: 'admin',
}, {
  id: 5,
  name: 'Andrey',
  role: 'user',
}];

И в зависимости от роли рисуется, если это обычный пользователь, компонент User, а если это администратор, то компонент Admin. Для key мы по прежнему используем index.


const Users = ({ users }) => {
  return users.map((user) => {
    const Component = user.role === 'admin' ? Admin : User;

    return (
      <Component
        key={index}
        name={user.name}
      />
    );
  }
}

И тут снова удалили пользователя Dmitriy.


Как думаете в этом случае какие логи будут в консоли?


Ответ я не буду раскрывать, оставлю это уже для самостоятельного изучения…


Заключение


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

Источник: https://habr.com/ru/post/527596/


Интересные статьи

Интересные статьи

Привет, друзья! Меня зовут Петр, я представитель малого белорусского бизнеса со штатом чуть более 20 сотрудников. В данной статье хочу поделиться негативным опытом покупки 1С-Битрикс. ...
Те, кто собираются открывать интернет-магазин, предварительно начитавшись в интернете о важности уникального контента, о фильтрах, накладываемых поисковиками за копирование материалов с других ресурсо...
Несмотря на то, что “в коробке” с Битриксом уже идут модули как для SOAP (модуль “Веб сервисы” в редакции “Бизнес” и старше), так и для REST (модуль “Rest API” во всех редакциях, начиная с...
В материале, первую часть перевода которого мы публикуем сегодня, речь пойдёт о том, как JavaScript-движок V8 выбирает оптимальные способы представления различных JS-значений в памяти, и о том, к...
Материал, перевод которого мы сегодня публикуем, раскрывает подходы, применяемые его автором при структурировании React-приложений. В частности, речь здесь пойдёт об используемой структуре папок,...