React Perf
Some patterns
React.Component
Always re-renders
React.PureComponent
Renders with shallow-equal comparison
- Иммутабельный стейт (редакс, компонента)
- Иммутабельные пропсы
=>
- Примитивы можно сравнивать по значению
- Объекты можно сравнивать по ссылке
=>
Используем shallow equal
(сравнение свойств через ===)
/* @flow */
import React from 'react';
const Counter = ({count}) => (
<div>
{count}
</div>
)
export default Counter
Было
/* @flow */
import React from 'react';
import {pure} from 'recompose'
const Counter = ({count}) => (
<div>
{count}
</div>
)
export default pure(Counter)
Стало
Pure Anti-patterns
Keep your links
const Controls = ({updateData}) => (
<section>
<Filters>
<Label text="Обновить данные">
<Button onClick={() => updateData()}>click me!</Button>
</Label>
</Filters>
</section>
);
Keep your links
const Controls = ({updateData}) => (
<section>
<Filters>
<Label text="Обновить данные">
<Button onClick={() => updateData()}>click me!</Button>
</Label>
</Filters>
</section>
);
Было
Стало
import {withHandlers} from 'recompose'
const enhance = withHandlers({
onClick: ({updateData}) => () => updateData(),
})
export const Controls = ({onClick}) => (
<section>
<Filters>
<Label text="Обновить данные">
<Button onClick={onClick}>click me!</Button>
</Label>
</Filters>
</section>
);
export default enhance(Controls)
const Item = ({updateItem, id}) => (
<article>
<Button onClick={() => updateItem(id)}>click me!</Button>
</article>
);
Keep your links
const Item = ({updateItem, id}) => (
<article>
<Button onClick={() => updateItem(id)}>click me!</Button>
</article>
);
Было
Стало
import {withHandlers} from 'recompose'
const enhance = withHandlers({
onClick: ({updateItem, id}) => () => updateItem(id),
})
const Item = ({onClick}) => (
<article>
<Button onClick={onClick}>click me!</Button>
</article>
);
export default enhance(Item)
const Items = ({updateItem, items}) => (
<article>
{items.map(({id}) => (
<Button onClick={() => updateItem(id)}>click me!</Button>
))}
</article>
);
Keep your links
const Items = ({updateItem, items}) => (
<article>
{items.map(({id}) => (
<Button onClick={() => updateItem(id)}>click me!</Button>
))}
</article>
);
Было
Стало
import {withHandlers} from 'recompose'
const enhance = withHandlers({
onClick: ({updateItem}) => event => updateItem(event.target.value),
})
const Items = ({onClick, items}) => (
<article>
{items.map(({id}) => (
<Button key={id} value={id} onClick={onClick}>click me!</Button>
))}
</article>
);
export default enhance(Items)
Не подходит стандартный html-тэг? (value, name и т.д.)
Используй dataset
import {withHandlers} from 'recompose'
const enhance = withHandlers({
onClick: ({updateItem}) => event => updateItem(event.target.dataset.id),
})
const Items = ({onClick, items}) => (
<article>
{items.map(({id}) => (
<Button key={id} data-id={id} onClick={onClick}>click me!</Button>
))}
</article>
);
export default enhance(Items)
import {connect} from 'react-redux'
import {updateUser} from '~/actions/users'
export const UsersSection = ({updateUser, users}) => (
<section>
<UserList users={users} updateUser={updateUser} />
</section>
)
export default connect(
({users, currentGender}) => ({
users: users.filter(({gender}) => gender === currentGender)
}),
{updateUser},
)(UsersSection)
Keep your links
import {connect} from 'react-redux'
import {createSelector} from 'reselect'
import {updateUser} from '~/actions/users'
export const UsersSection = ({updateUser, users}) =>
<UserList users={users} updateUser={updateUser} />
const getUsersByGender = createSelector(
({users}) => users,
({currentGender}) => currentGender,
(users, currentGender) => users.filter(({gender}) => gender === currentGender),
)
/* with ramda helpers
const getUsersByGender = createSelector(
prop('user'),
prop('currentGender'),
(users, currentGender) => filter(propEq('gender', currentGender), users),
)
*/
export default connect(
state => ({users: getUsersByGender(state)}),
{updateUser},
)(UsersSection)
Стало
const Header = () => (
<section>
<Filters>
<Label text="Даты">
<DatePickerDropdown
presets={[]}
range
placeholder="Выберите даты"
format="YYYY-MM-DD"
name={{from: 'fromDate', to: 'toDate'}}
/>
</Label>
</Filters>
</section>
);
Keep your links
Стало
const presets = []
const name = {from: 'fromDate', to: 'toDate'}
const Header = () => (
<section>
<Filters>
<Label text="Даты">
<DatePickerDropdown
presets={presets}
range
placeholder="Выберите даты"
format="YYYY-MM-DD"
name={name}
/>
</Label>
</Filters>
</section>
)
const Header = ({presets = [], name = {from: 'fromDate', to: 'toDate'}}) => (
<section>
<Filters>
<Label text="Даты">
<DatePickerDropdown
presets={presets}
range
placeholder="Выберите даты"
format="YYYY-MM-DD"
name={name}
/>
</Label>
</Filters>
</section>
);
Keep your links
import {defaultProps} from 'recompose'
const enhance = defaultProps({
presets: [],
name: {from: 'fromDate', to: 'toDate'}
})
const Header = ({name, presets}) => (
<section>
<Filters>
<Label text="Даты">
<DatePickerDropdown
presets={presets}
range
placeholder="Выберите даты"
format="YYYY-MM-DD"
name={name}
/>
</Label>
</Filters>
</section>
)
export default enhance(Header)
Стало
const List = ({children, className}) => (
<div className={className}>
{React.cloneElement(children, {'name': 'list'})}
</div>
)
Keep your links
const List = ({children, className}) => (
<div className={className}>
{React.cloneElement(children, {'name': 'list'})}
</div>
)
Было
Стало
const customProps = {'name': 'list'}
const List = ({children, className}) => (
<div className={className}>
{React.cloneElement(children, customProps)}
</div>
)
const List = ({children, className, onChange}) => (
<ul className={className}>
{Children.map(children, ((child, i) => (
<li key={i}>{React.cloneElement(child, {onChange, 'data-index': i})}</li>
)))}
</ul>
)
Keep your links
Решения присылайте на enload@yandex-team.ru
Итого
- Декомпозиция
- Иммутабельные стейты и пропсы
- Сравниваем пропсы через:
- pure
- shouldUpdate
- onlyUpdateForKeys
- и т.д.
-
Храним ссылки
- defaultProps, withProps и пр.
- для колбэков: withHandlers, withStateHandlers и т.д.
- reselect
- Мемоизация вычислений через reselect
- Следим за производительностью через:
- devtools
- WhyDidYouUpdate
- и т.д.
- Внимательно относимся к проверкам, не стоит допускать несколько сравнений
- Например, в connect уже есть сравнение, соотв. либо не нужно компонент делать pure, либо нужно использовать connect без сравнения
- Не все компоненты обязаны быть pure. Важнее делать такими целые ветки
React Perf
By Artur Kenzhaev
React Perf
- 741