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

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

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

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

Functionality1:

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;

Functionality2:

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. Есть ли другие способы сделать то же самое, кроме этих двух? Если да, то это рекомендуется?

Ответ 1

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

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

// 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 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 не восстанавливаются на каждом рендере, и вам не нужно мусор собирать ненужные объявления переменных.

Ответ 3

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

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

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

Ответ 4

Как правило, оператор 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>;

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

Я бы пошел с 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>
      );
    }
  }
}

Таким образом, мы можем использовать этот компонент Spanner как общий компонент, совместно используемый для разных компонентов. И в случае изменения 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. Опять же, вот как я это сделаю.

Ответ 8

Функция 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>
    )
  }
}