@sharkdest

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

Здравствуйте, проблема заключается в следующем:
Пользуюсь react-csv в React приложении:

...
render() {
 return (
 ...
 {rows.map(row => (
                <CSVLink
                  key={row.number}
                  className="btn btn-secondary btn-download"
                  data={rows}
                  separator={";"}
                  filename="netpositions.csv"
                >
                  <span className="oi oi-data-transfer-download" />
                </CSVLink>
 ))}
...
 )
}


Без key={row.number} - сохраняется пустой файл, посоветовали добавить ключ.
И все работает хорошо, если бы не:
5c498a7a9a6a6116161541.png
Сколько строк в таблице - столько кнопок и будет (нужна одна кнопка), оно и не странно, т.к. использую .map().
Еще плюс ко всему:
5c498a9fde187845166712.png

Как мне решить проблему по нормальному?
Заранее спасибо.
  • Вопрос задан
  • 867 просмотров
Решения вопроса 2
@askhat
На вопрос ответил автор вопроса, я в свою очередь попытаюсь объяснить что произошло.

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

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.
Ответ написан
@sharkdest Автор вопроса
Спасибо Алексей Казаков.
Решение:

...
render() {
 let keys = {};
 rows.map(row => {
    keys.key = row.number;
 });

 return (
 ...
                <CSVLink
                  key={keys.key}
                  className="btn btn-secondary btn-download"
                  data={rows}
                  separator={";"}
                  filename="netpositions.csv"
                >
                  <span className="oi oi-data-transfer-download" />
                </CSVLink>
 ))}
...
 )
}


Если кто-то предложит другое решение - буду признателен.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы