Часто при написании компонентов приходится использовать мапы для рендера списков.
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