Самый эффективный способ отрисовки JSX-элементов при итерации массива данных в React

У меня есть массив, содержащий объекты. Я создаю карту этого массива, чтобы отображать имена с помощью компонента span.

let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];

Я использовал следующие две разные функции для итерации этого массива объектов и использовал карту для рендеринга элементов JSX.

Функциональность1:

import React, { Component } from 'react';
class App extends Component {

  render() {
    let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
    const items = data.map((key, i) => {
      return <span key = {key.id}>{key.name}</span>;
    });
    return (
      <div>
        {items}
      </div>
    );
  }
}

export default App;

Функциональность2:

import React, { Component } from 'react';
class App extends Component {

  render() {
    let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
    let rows = [];
    data.map((key, i) => {
      rows.push(<span key = {key.id}>{key.name}</span>);
    });
    return (
      <div>
        {rows}
      </div>
    );
  }
}


export default App;

Выше я знал два разных способа использования map и рендеринга JSX-элементов. Есть ли другие способы сделать то же самое, кроме этих двух? Если да, то что рекомендуется?

«Functionality2» создает массив при вызове .map, который никогда не используется. Переключитесь на data.forEach, чтобы не создавать ненужный массив.

Ross Allen 07.09.2018 07:01

но это только при присвоении ссылки правильно? как const array = data.map (поправьте меня, если я ошибаюсь

Hemadri Dasari 07.09.2018 07:14

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

Ross Allen 07.09.2018 07:24

Хорошо понял. Спасибо. Можете ли вы помочь мне с другими способами рендеринга элементов jsx, когда мы рендерим их в итерации. Помимо того, что я упомянул, и других, упомянутых ниже

Hemadri Dasari 07.09.2018 07:27

Составьте быстрое JsPerf сравнение ваших двух подходов, а также некоторых вариантов. Functionnality1 - ваш лучший выбор.

JSTL 07.09.2018 22:02
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
17
5
4 900
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Первый способ лучше.

  1. Array.prototype.map создает массив за кулисами и возвращает его после применения модификации к каждому элементу. Функциональность-1 создает два массива, а Функциональность-2 создает три.

  2. Функциональность-1 читается лучше. Так обычно пишется код React. Для такого простого элемента, как этот, я бы сохранил определение const для элементов и поместил оператор карты в JSX для возврата напрямую.

Я бы сделал это

const data = [{id: 1, name: 'a'}, {id: 2, name: 'b'}];

export default class App extends PureComponent {
  render() {
    return (
      <div>
        {data.map(({ id, name }) => <span key = {id}>{name}</span>)}
      </div>
    );
  }
}

Теперь ваш data не восстанавливается при каждом рендеринге, и вам не нужно собирать мусор для ненужных объявлений переменных.

Проголосовали. Я бы добавил, что если вы хотите избежать упаковки элементов <span> в <div>, вы можете использовать фрагменты реакции: reactjs.org/docs/fragments.html

jackdbd 13.09.2018 23:37

Вы можете использовать это для лучшего понимания

 const data = [{id: 1, name: 'a'}, {id: 2, name: 'b'}];

export default class App extends React.Component {
  render() {
    return (
      <div>
        {data.map((data, dataIndex ) => <span key = {dataIndex}>{data.name}</span>)}
      </div>
    );
  }
}

Здесь можно понять, что имя принадлежит атрибуту.

index, предоставленный map-method, не рекомендуется использовать в качестве пары "ключ-значение".

Jimi Pajala 07.09.2018 13:50
Ответ принят как подходящий

В основном я следую этому правилу:

Создайте компонент, который отображает элементы

// in some file
export const RenderItems = ({data}) => {
  return data && data.map((d, i) => <span key = {d.id}>{d.name}</span>) || null
}

Подключите RenderItems

import { RenderItems } from 'some-file'

class App extends Component {
  render() {
    // you may also define data here instead of getting data from props
    const { data } = this.props
    return (
      <div>
        <RenderItems data = {data} />
      </div>
    )
  }
}

Прикрепите данные к компоненту

const data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}]
<App data = {data} />

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


Кроме того, я бы не стал создавать id только для использования ключа:

const data = [{"name": "Hi"}, {"name": "Hello"}]
//... and when using index as key
.map((d, i) => <span key = {'item-'+i}>
// or,
.map((d, i) => <span key = {'item-'+i+'-'+d.name}>

См. эта почта, почему я следую этому синтаксису при использовании индекса в качестве ключа.


Обновлять:

Если вы хотите избежать использования ненужных тегов html, вы можете использовать React.Fragment

export const RenderItems = ({data}) => {
  return data && 
    data.map(
      (d, i) => <React.Fragment key = {d.id}>{d.name}</React.Fragment>
    ) || null
}
// and when rendering, you just return the component
return <RenderItems data = {data} />

Примечание:

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

Пример использования <></>:

<>{d.name}</>

Это будет отображено значение d.name в HTML без каких-либо тегов. Это считается лучшим, когда мы специально трансформируем наш существующий дизайн, чтобы реагировать на приложение. Или могут быть другие случаи. Например, мы собираемся отобразить список определений:

<dl>
  <dt></dt>
  <dd></dd>
  <dt></dt>
  <dd></dd>
  <dt></dd>
</dl>

И мы не хотим прикреплять ненужный тег html, тогда использование Fragment упростит нам жизнь:

Пример:

<>
  <dt>{d.term}</dt>
  <dd>{d.definition}</dd>
</>

Наиболее важным случаем будет рендеринг элемента td в tr (компонент TR). В противном случае мы нарушаем правило HTML. Компонент не будет отображаться должным образом. В ответ он выдаст вам ошибку.

Обновление2:

Кроме того, если у вас есть длинный список реквизита, как показано ниже:

const {
  items,
  id,
  imdbID,
  title,
  poster,
  getMovieInfo,
  addToFavorites,
  isOpen,
  toggleModal,
  closeModal,
  modalData,
} = props

Вы можете подумать о деструктуризации, например:

const { items, ...other } = props
// and in your component you can use like:
<div modalData = {other.modalData}>

Но лично я предпочитаю использовать первый пример кода. Это потому, что при разработке мне не нужно каждый раз оглядываться на другой компонент или искать консоль. В данном примере есть такой ключ, как modalData = {}, поэтому мы легко поддерживаем modalData = {other.modalData}. Но что, если нужно кодировать как <div>{modalData}</div>? Тогда вы также можете согласиться с моими предпочтениями.

Спасибо. Я никогда не пробовал так, пока не увидел это в первый раз.

Hemadri Dasari 07.09.2018 17:56

Рад помочь вам.

Bhojendra Rauniyar 07.09.2018 17:57

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

Estus Flask 08.09.2018 04:09

@estus при использовании с некоторым постоянным значением не считается плохим.

Bhojendra Rauniyar 08.09.2018 05:33

Как правило, оператор for или while является наиболее эффективным способом перебора массива. То, как небольшой массив обрабатывается в некритичном месте, можно считать микрооптимизацией.

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

Пока это антипаттерн:

let rows = [];
data.map((key, i) => {
  rows.push(<span key = {key.id}>{key.name}</span>);
});

map должен отображать элементы массива в другие значения (отсюда и название), а не выполнять итерацию массива вместо forEach или другого цикла. Эту проблему можно отследить с помощью правила ESLint array-callback-return.

Компонент не имеет состояния и не обязательно должен быть классом Component. Это может быть функциональная составляющая или класс PureComponent. Поскольку data постоянен, его не нужно назначать для каждого рендера:

const data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];

const App = props => <div>
  {data.map(({ id, name }) => <span key = {id}>{name}</span>)}
</div>;

Спасибо. Что, если я хочу сгенерировать элементы jsx с функциями обработчика событий в итерации. Могу ли я использовать для этого компонент без сохранения состояния?

Hemadri Dasari 08.09.2018 05:58

@ Think-Twice Возможно, да, в зависимости от того, что эти обработчики должны делать. Можете ли вы дополнить вопрос другим примером, который это демонстрирует?

Estus Flask 08.09.2018 12:31

первый способ правильный. используйте функцию карты для итерации по массиву.

export default class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
          data: [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
        };
 }
    render(){
        return(
            <div>           
                {this.state.data.map((data,index)=>
                      <span key = {data.id}>{data.name}</span>
                )}             
        );
    }
}

Я бы пошел с map внутри return(...), поскольку карта возвращает массив. Он более чистый и читаемый, к чему я лично стремлюсь.

Чтобы добавить к ответу, если массив data может измениться, я бы пошел с созданием нового компонента дампа без сохранения состояния:

const Spanner = props => { return <span key = { props.data.id }>{ props.data.name }</span> };

class App extends Component {
  render() {
    let data = [{"id": "01", "name": "Hi"}, {"id": "02", "name": "Hello"}];
      return (
        <div>
          { 
            data.map( (item, ind) => {
              return (<Spanner key = {item.id} data = {item.name} />);
            })
          }
        </div>
      );
    }
  }
}

Таким образом, мы можем использовать этот компонент гаечного ключа как общий компонент, совместно используемый разными компонентами. И в случае, если data меняется со временем (что происходит в большинстве случаев), вы можете написать функцию-оболочку для компонента Spanner и вызывать новую функцию, где бы это ни было. data=[{"id": "01", "name": "Hi", "userType": "Admin"}, {"id": "02", "name": "Hello", "userType": "Client"}];

const UserWidget = (props) => {
  return (
    <div>
      <h4>{ props.type }</h4>
      <Spanner key = { props.key } data = { props.name }/>
    </div>
  );
}
// now both the UserWidget and the Spanner Components can be used wherever required
// say a dashboard Component wants to use the UserWidget
class Dashboard extends Component {
  render() {
    let data = [{"id": "01", "name": "Hi", "userType": "Admin"}, {"id": "02", "name": "Hello", "userType": "Client"}];
      return (
        <div>
          { 
            data.map( (item, ind) => {
              return (<UserWidget type = { item.userType } key = {item.id} data = {item.name} />);
            })
          }
        </div>
      );
    }
  }
}

Таким образом, вы не повторяете себя, и ваш компонент приложения по-прежнему имеет ожидаемое поведение, даже если теперь новый массив data имеет новый ключ userType. И снова вот как я это сделаю.

Функцию toSpan можно использовать повторно.

class App extends React.Component {
  render() {
    const data = [{id: `01`, name: `Hi`}, {id: `02`, name: `Hello`}]
    const toSpan = ({id, name}) => <span key = {id}>{name}</span>
    return (
      <div>{data.map(toSpan)}</div>
    )
  }
}

Другие вопросы по теме