Повышаем производительность рендера списков в React-компонентах

Повышаем производительность рендера списков в React-компонентах

Часто при написании компонентов приходится использовать мапы для рендера списков.

react
items.map(({ value }, i) =>
  <div key={i} styleName='value'>{value}</div>
)

Элемент списка может содержать много разметки.

items.map(({ name, value }, i) =>
  <div key={i} styleName='item'>
    <div styleName='name'>{name}</div>
    <div styleName='value'>{value}</div>
  </div>
)

Обычно всё это нужно выносить в отдельный компонент и переиспользовать.

items.map(({ name, value }, i) =>
  <Item
    key={i}
    name={name}
    value={value}
  />
)

Использование индекса в ключе — антипаттерн.

Я показывал поэтапно, но лучше сразу выбросить текущий индекс из key и заменить его на уникальный id. React не будет перерендеривать уже имеющийся компонент с этим же ключом, хотя в мапе пропсы поменялись. Побочным эффектом, например, может быть дублирование или изменение порядка в списке и т.п.

items.map(({ id, name, value }) =>
  <Item
    key={id}
    name={name}
    value={value}
  />
)

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

items.map(({ id, name, value }) =>
  <div key={id} styleName='item'>
    <Item name={name} value={value} />
  </div>
)

Вроде бы всё норм, но не так давно @laiff поделился опытом по этому поводу.

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

Если в мапе находится сразу компонент, то это процесс отсекается до этого алгоритма, т.к. есть ReactInstance с shouldComponentUpdate и другими проверками типа key. Т.е. снова нужно выноcить в компонент. Получается то же самое, что было и раньше, только в это раз в вёрстке есть отступы.

items.map(({ id, name, value }) =>
  <Item
    key={id}
    name={name}
    value={value}
  />
)

Использование компонентов в мапах также производительней при добавлении различных обработчиков, например, на событие onClick. Если с обёрткой, это делалось путём вызова функции и передачи ей необходимых данных, создавая каждый раз новую функцию:

items.map(({ id, name, value }) =>
  <div key={id} styleName='item'>
    <Item
      name={name}
      value={value}
      onClick={onClick(id)}
    />
  </div>
)

То напрямую с компонентом передаются все необходимые данные и сама функция, которая внутри создастся один раз:

items.map(({ id, name, value }) =>
  <Item
    key={id}
    id={id}
    name={name}
    value={value}
    onClick={onClick}
  />
)

Отдельно стоит упомянуть о либах pure-render-decorator и recompose, которые добавляют компоненту проверку пропсов в shouldComponentUpdate и решают, когда нужен перерендер, тем самым увеличивая скорость работы приложения. В будущем они не понадобятся, т.к. в React это будет работать по умолчанию из коробки.

Всё вместе в итоге.

// list/index.jsx
import { Component } from 'react'
import pure from 'pure-render-decorator'
import css from 'react-css-modules'
import styles from './styles.sss'
import ListItem from './item'

@css(styles)
@pure
export default class List extends Component {
    // ...

  renderItem = ({ id, name, value }) =>
    <ListItem
      key={id}
      id={id}
      name={name}
      value={value}
      onClick={this.props.onClick}
    />

  render() {
    return <div styleName='list'>
      {this.props.items.map(this.renderItem)}
    </div>
  }
}
// list/item.jsx
import { Component } from 'react'
import pure from 'pure-render-decorator'
import css from 'react-css-modules'
import styles from './styles.sss'
import Item from '../item'

@css(styles)
@pure
export default class ListItem extends Component {
    // ...

  onClick = () => this.props.onClick(this.props.id)

  render() {
    const { id, name, value } = this.props

    return <div styleName='item'>
      <Item
        name={name}
        value={value}
        onClick={this.onClick}
      />
    </div>
  }
}
// item/index.jsx
import pure from 'pure-render-decorator'
import css from 'react-css-modules'
import styles from './styles.sss'

function Item({ name, value }) {
  return <div styleName='item'>
    <div styleName='name'>{name}</div>
    <div styleName='value'>{value}</div>
  </div>
}

export default pure(css(Item, styles))

Вот так готовятся компоненты в нашей кухне. А как вы это делаете?

Человек из космоса, @felixexter

Будь в курсе

Каждый месяц cвежие статьи, подкасты, новости веб-разработки, вакансии и интересные события. Никакого спама. Только по делу.

Отправляя данную форму, я подтверждаю своё согласие на получение рекламных и информационных материалов, а также факт своего ознакомления и согласия с Политикой конфиденциальности

Иллюстрация человека, читающего газету

Читайте также

Комментарии

Продолжая пользование данным Сайтом, вы даете свое согласие на сбор и обработку технической информации (cookies). Подробнее о cookies читайте вПолитике конфиденциальности.