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

Повышаем производительность рендера списков в React-компонентах
Иллюстрация говорящего человека

Ежемесячная рассылка CSSSR

Новости, свежие статьи и многое другое

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


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

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

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

Ежемесячная рассылка CSSSR

Новости, свежие статьи и многое другое

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

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

Комментарии

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