• Чем заменить функцию .map() в моем случае?

    askhat
    @askhat
    SDE @ Akvelon
    На вопрос ответил автор вопроса, я в свою очередь попытаюсь объяснить что произошло.

    Рендер в реакте всегда идёт по наименее трудозатратному пути, а именно вычисляет дельту (разницу) между новым стейтом и предыдущим, если таковой имеется. Например:

    class TodoList extends React.Component {
      state = {
        todos: [
          'Commit',
          'Push'
        ]
      }
      render() {
        return <ul>
          {this.state.todos.map(item => {
            return <li>{ todo }</li>
          }
        </ul>
      }
    }


    Если стейт компонента изменится, скажем при добавление элемента в начало списка todos, так что он станет таким:

    const todos = [
      'Init',
      'Commit',
      'Push'
    ]


    Реакт вычислит два древа VirtualDOM:

    // Начальный стейт
    <ul>
      <li>Commit</li>
      <li>Push</li>
    </ul>
    // Добавлен элемент
    <ul>
      <li>Init</li> // <- разница начинается здесь и до конца древа
      <li>Commit</li>
      <li>Push</li>
    </ul>


    Здесь выполняется работа которой можно было бы избежать. К примеру если бы элемент был добавлен в конец списка:

    const todos = [
      'Commit',
      'Push',
      'Merge'
    ]


    То реакт получил бы на сравнение другие два древа элементов:

    // Начальный стейт
    <ul>
      <li>Commit</li>
      <li>Push</li>
    </ul>
    // Добавлен элемент
    <ul>
      <li>Commit</li>
      <li>Push</li>
      <li>Merge</li> <- разница начинается здесь, от начала и до сих по ничего не менялось
    </ul>


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

    Совершенно очевидно что <li>Commit</li> и <li>Push</li> не менялись, однако реакт недостаточно умён чтобы это понять. Чтобы помочь ему следует воспользоваться специальным пропом key={}. Он может быть значением любого типа, единственно требование — значение должно стабильно идентифицировать соответствующие данные.

    Если бы компонент выглядел так:

    class TodoList extends React.Component {
      state = {
        todos: [
          { id: 0, text: 'Commit' },
          { id: 1, text: 'Push' }
        ]
      }
      render() {
        return <ul>
          {this.state.todos.map(item => {
            return <li key={todo.id}>{ todo.text }</li>
          }
        </ul>
      }
    }


    То добавление элемента в начало массива, породило бы следующий стейт:

    const todos = [
      { id: 2, text: 'Init' },
      { id: 0, text: 'Commit' },
      { id: 1, text: 'Push' }
    ]


    И, снова, два древа элементов:

    // Начальный стейт
    <ul>
      <li>Commit</li> // id 0
      <li>Push</li> // id 1
    </ul>
    // Добавлен элемент
    <ul>
      <li>Init</li> // id 2 новый элемент отобразится в начале
      <li>Commit</li> // id 0
      <li>Push</li> // id 1 
    </ul>


    Благодаря подсказке, реакт не учтёт в дельте элементы чьи идентификаторы не изменились, и, как следствие, не будет делать лишних действий.

    Таким образом использовать индекс в массиве в качестве ключа — не лучшая идея, особенно если массив будет меняться. По той же причине не следует использовать Math.random() в качестве ключа, так вы почти гарантировано будете всегда получать нестабильные идентификаторы.

    Подробнее об этом можно почитать здесь — Reconciliation.
    Ответ написан
    5 комментариев