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


image

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

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

comments powered by Disqus