29 Июля вышел React 15.3, и первым пунктом в release-notes значилось добавление поддержки React.PureComponent, который заменяет своего предшественника pure-render-mixin. В этой статье обсудим, почему же этот компонент так важен и где его использовать.
Это один из самых значительных способов оптимизации react-приложений, который можно довольно легко и быстро реализовать. Использование pure-render-mixin дает ощутимый прирост в производительности, так как сокращается количество рендеров в приложении, а значит и react, в свою очередь, производит намного меньше операций.
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 {…}
… и они продолжали спокойно работать, да еще с увеличенной производительностью.
Так что пробуйте и используйте, компонент очень полезный.
Автор: REDMADROBOT