![](https://habrastorage.org/files/702/de5/473/702de5473cf84371818c73c2894321d7.png)
29 Июля вышел React 15.3, и первым пунктом в release-notes значилось добавление поддержки React.PureComponent, который заменяет своего предшественника pure-render-mixin. В этой статье обсудим, почему же этот компонент так важен и где его использовать.
Это один из самых значительных способов оптимизации react-приложений, который можно довольно легко и быстро реализовать. Использование pure-render-mixin дает ощутимый прирост в производительности, так как сокращается количество рендеров в приложении, а значит и react, в свою очередь, производит намного меньше операций.
![image](https://habrastorage.org/getpro/habr/post_images/2f7/1a0/470/2f71a04703d2952eb29936a48f317c9b.gif)
PureComponent изменяет lifecycle-метод shouldComponentUpdate, автоматически проверяя, нужно ли заново отрисовывать компонент. При этом PureComponent будет вызывать рендер только если обнаружит изменения в state или props компонента, а значит во многих компонентах можно менять state без необходимости постоянно писать
if (this.state.someVal !== computedVal) {
this.setState({someVal: computedVal})
}
В исходниках React при условии, что компонент является «Pure», и проводится такая проверка:
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}
Использование shallowEqual говорит о том, что происходит неглубокая проверка params и state, так что сравнение не будет происходить по глубоко вложенным объектам, массивам.
Глубокое сравнение — очень затратная операция. Если бы PureComponent каждый раз ее вызывал, то он бы приносил больше вреда, чем пользы. Никто не мешает использовать проверенный shouldComponentUpdate, чтобы вручную определить необходимость нового рендера. Самый простой вариант — прямое сравнение параметров.
shouldComponentUpdate(nextProps, nextState) {
return nextProps.user.id === props.user.id;
}
Также можно использовать immutable данные. Сравнение в таком случае становится очень простым, так как имеющиеся переменные не изменяются, а всегда создаются новые. Библиотеки вроде Immutable.js — наш верный союзник.
Особенности применения
PureComponent экономит нам время и позволяет не писать лишний код, но не является панацеей. Важно знать особенности его применения, иначе полезность теряется. Так как PureComponent предполагает неглубокую проверку, изменения его props и state могут остаться проигнорированными. К примеру, в родительском компоненте есть рендер и обработчик клика:
handleClick() {
const items = this.state.items;
items.push('new-item');
this.setState({items: items});
}
render() {
return (
<div>
<button onClick={this.handleClick} />
<ItemList items={this.state.items} />
</div>
);
}
Если компонент ItemList сделать PureComponent, то при изменении items после нажатия кнопки ничего не будет происходить. Это случается из-за того, что this.state.items при сравнении будет равен старой версии this.state.items, хотя содержимое его поменялось. Однако это легко исправить, убрав мутации, например вот так:
handleClick() {
this.setState(prevState => ({
words: prevState.items.concat(['new-item'])
}));
}
PureComponent будет всегда заново отрисовывать компоненты, если будет получать ссылки на разные объекты. Это значит, что если мы не хотим терять преимущества PureComponent, следует избегать подобных конструкций:
<Entity values={this.props.values || []}/>
Новый массив, хоть он и пустой, будет всегда заставлять компонент отрисовываться заново.
Избежать такого очень просто, к примеру, с помощью DefaultProps, в котором можно задать изначально пустое состояние передаваемой переменной. Также очень часто можно увидеть такие компоненты:
<CustomInput onChange={e => this.props.update(e.target.value)} />;
При их создании всегда будет создаваться новая функция, а значит и PureComponent будет видеть каждый раз новые данные. Это лечится, например, bind'ом нужной функции в конструкторе компонента.
constructor(props) {
super(props); this.update = this.update.bind(this);
}
update(e) {
this.props.update(e.target.value);
}
render() {
return <MyInput onChange={this.update} />;
}
Также любой компонент, который содержит дочерние элементы, созданные в JSX, будет всегда выдавать false на shallowequal проверках.
Важно помнить, что PureComonent пропускает отрисовку не только самого компонента, но и всех его “детей”, так что безопаснее всего применять его в presentational-компонентах, без “детей” и без зависимости от глобального состояния приложения.
Что в итоге
На самом деле переход на PureComponent является довольно простым, если знать ряд особенностей, связанных скорее с самим JS, нежели с React. Во многих компонентах я заменял:
class MyComponent extends Component {…}
на:
class MyComponent extends PureComponent {…}
… и они продолжали спокойно работать, да еще с увеличенной производительностью.
Так что пробуйте и используйте, компонент очень полезный.
EDIT
Спасибо raveclassic за полезное замечание. В случае, если pure-компонент имеет детей, все дочерние компоненты, зависящие от смены контекста, не будут реагировать на изменения, если в родительском pure-компоненте не будет объявлен contextTypes.
Поделиться с друзьями
Комментарии (10)
raveclassic
26.12.2016 18:52Стоило упомянуть проблемы со сменой контекста при использовании pure-rendering.
Kresent
26.12.2016 19:11Спасибо за полезное замечание.
raveclassic
27.12.2016 10:37shouldComponentUpdate, следовательно и pureComponent, игнорирует изменения в context, проверяя лишь изменения в state и props.
На самом деле, это тоже не совсем так. Контекст приходит третьим аргументом в shouldComponentUpdate, но тогда и только тогда, когда он явно указан через contextTypes. Проблема в том, что не все компоненты это делают (и это разумно), и, например, промежуточный PureComponent, который рендерит детей, зависящих от контекста (и смены значений в нем), если в нем не объявлены contextTypes, просто проигнорирует апдейт.
Существует вполне себе решение (и вроде так сделали в react-router) — хранить в контексте неизменяемую ссылку на инстанс эмиттера, и общаться событиями изменения через него. В детях подписываемся и обновляем стейт, можно даже в HOC обернуть.raveclassic
27.12.2016 10:53Собственно, так же сделано в react-redux — в контексте лежит инстанс стора, а connect на любом уровне подписывается на его изменения и обновляет оборачиваемые компоненты с нужными данными из селекторов.
CrazyOne
27.12.2016 01:31+1Как-то мимо обошел изменения в React, просто обновился для галочки. Спасибо за статью!
MrCheater
PureComponent или "чистый компонент" подразумевает, что
state
внутри не будет, что весьма логично. "Чистый" — значит "без состояния".Примеры несколько неудачны.
Vovchikvoin
Простите но вы абсолютно не правы. Чистый компонент — это когда render зависит только от state и props. То о чем вы говорите, называется — stateless компонент.
MrCheater
Простите, а от чего кроме
props
иstate
может зависеть результат работыrender
?Если вы про
context
, то юзать его можно и в PureComponent, и в Component.raveclassic
Честно говоря, от чего угодно:
const Foo = props => Math.random()
.Проблема в том, что команда реакта в свое время позволила себе обозвать технику проверки аргументов в процессе, называемом ими
reconciliation
, какpure rendering
. Они даже дальше пошли — придумалиPureComponent
.Но, первое, о чем навевает мысли слово
pure
— это чистые функции ФП. По этой аналогии оно и придумано. Пересечение, конечно, есть, но до тех пор, пока в языке отсутствуют декларативные эффекты, те же самыеelm/purescript
не дадут вам безнаказанно встроить в функцию генератор случайных чисел. Вам придется декларативно указать это в типе, и вот тогда функция продолжить быть чистой, только с эффектом в возвращаемом значении.В общем, отсюда и вся путаница.
MrCheater
Я пал жертвой злых маркетологов и их "модных" названий :-(