React.jsでPureRenderをHOCパターン化したらReduxでも捗った件
React.jsでのパフォーマンス最適化に関しては、昔はPureRenderMixinを使ってRenderを最小化しろと言われてた。
でもある時、公式ブログでやっぱMixin難しくね?って話が出てきて、shallowCompare
を使えとなった。
var shallowCompare = require('react-addons-shallow-compare'); var Button = React.createClass({ shouldComponentUpdate: function(nextProps, nextState) { return shallowCompare(this, nextProps, nextState); }, // ... });
的な。
一方Gihubの星がやたら多いReduxさんは
Renderingの最適化はreact-reduxに任せておけばえーねんと宣まい、 ReactのComponentは全部Stateless Functionにしとけばいい(classにする必要はない)と言う。
const Button = (props) => { /* ... */ }
確かにReduxをちゃんと実装すればshouldComponentUpdate
はほとんど気にしなくていい。
が、やっぱりちゃんとやろうと思うと必要な場面が出てくる。 繰り返し項目とかね。
react-redux
は内部的にshallowCompare
を使うので、connect
でbindしたプロパティがArrayの場合、
Arrayの一部だけ変化だとしてもArrayそのもののインスタンスが変われば子要素全てが再レンダリングされてしまう。
const list = (entities) => entities.map((entity) => ( <MyListItem key={ entitye.id } entity={ entity } /> )
みたいな場合、entitiesの一部だけが変わった場合の最適化はそれぞれのMyListItem
側で最適化しなければならない。
この場合、MyListItemがContainer Componentだったら同じくreact-redux
に最適化に任せれば良いのだけど、Presentational Componentだったら結局、自前でshouldComponentUpdate
判定する必要がある。
でもそのためだけにStateless Functionをやめてclass化するのはなんかやだ。
const MyListItem = (props) => { /* ... */ }
的なシンプルさを保ちたい。
どうすれば良いのか。
そこでHOC (Higher-Order Components)ですよ。
shouldComponentUpdate
を個別に書くのをがめんどくさいのならwrapperで共通化しちゃえば良い。
つまり、
const asPure = (Component) => class AsPure extends React.Component { shouldComponentUpdate (nextProps) { const s = this let { props } = s return !shallowEqual(props, nextProps) } render () { const s = this let { props } = s return React.createElement(Component, props) } } export default asPure
的なwrapper関数を用意して、
const MyListItem = (props) => { /* ... */ } export default asPure(MyListItem)
にすりゃいいじゃないか!
と言うことに気がついた。
しかしあれだな、書いてて思ったけど、 HOCだのPresentational Componentだの、どうしてReact周りはこんな難しい単語まみれになっちゃうのやら。。。