React: Google Chrome for macOSでメモリリークが起こる

Google Crome for macOSで、メモリの消費が増え続けるという現象に遭遇しました。英語を含めても情報が少なかったので、備忘録としてまとめます。

# [Simple app causing a memory leak?](https://github.com/facebook/create-react-app/issues/4037)
(2018/02/17)

リスナーが増え続けて、メモリが費やされるという問題が示されました。

それに対して、ReduxやCreate React Appの開発に携わった[Dan Abramov](https://github.com/gaearon)氏は、つぎのようにコメントしています(なお「React開発者ではありません」とは、ご自身の紹介文)。メモリ消費が増えているのはリスナーを膨大につくっているからだというのです。メモリリークではなく、開発ビルドでのみ起こり、プロダクション版では生じないと説明しています。また、リスナーもガベージコレクションにより片づけられるそうです。

[Dan Abramov氏によるコメント(2018/02/19)](https://github.com/facebook/create-react-app/issues/4037#issuecomment-366538254)
> Fairly sure this isn't a memory leak but just excessive listener creation. It only happens in development builds, not in production. You can see these thread for more details: [facebook/react#10576](https://github.com/facebook/react/issues/10576) and [facebook/react#12141](https://github.com/facebook/react/issues/12141). We do create listeners but we expect GC to clean them up.

この問題はDan Abramov氏が同日にクローズしました。

# [Apparent Slow (25 Bytes per Render) Memory Leak with React / React-Dom?](https://github.com/facebook/react/issues/10576)
(2017/08/31)

[John Tucker](https://github.com/facebook/react/issues/10576)氏により、メモリリークがわずかずつ増えるという問題が報告されました。簡単なテストサンプルで、現象が再現されています。けれど、2017年8月31日に同氏のコメントで撤回され、クローズされました。

テストの仕方がまずかったということです。ただし、ミスがあったというのではありません。ReactでなくChromeデベロッパーツールの問題だというのです。デベロッパーツールのメモリ消費が大きく、ツールを閉じるだけでタスクマネージャのメモリ使用量は減りました。再度ツールを開くと1時間につき1MBほどずつアプリケーションのメモリ使用量が増えたということです。

ガベージコレクションを実行できるようデベロッパーツールを開いていたものの、閉じればメモリ消費は適宜減り、ガベージコレクションが適切に働いたといいます。

[John Tucker氏によるコメント(2017/08/31)](https://github.com/facebook/react/issues/10576#issuecomment-326141622)
> NEVERMIND.... It appears that my testing methodology was broken and it does NOT look like there is a problem with React (rather a problem with Chrome). I just so happened to be testing my actual application and noticed that my Chrome "Tab: Developer Tools" was insanely large (70+ MB). When I closed it, I noticed that Chrome's Task Manager stopped showing the steady growth in the application that I was troubleshooting. I was seeing growth like 1 MB per hour in the application itself when Developer's Tools was open.

> I went back to the React test that I did here and this time left the Chrome Developer's Tools CLOSED and just watched the Task Manager. Originally, I left Developer's tools open so that I could trigger a GC.

> Without Developer's tools open, I saw the automatic GC process get triggered every so often and the memory would regularly drop way down to 5MB.


# [React16 dev memory leak on render with event listeners](https://github.com/facebook/react/issues/12141)
(2018/02/03)

スクロールやキーボード入力のイベントリスナーが加えられたとき、すばやくイベントを起こすとメモリリークにより反応が遅れると報告されました。ただし、ほかの人たちは適切に再現できなかったようです。それに対して、Dan Abramov氏からコメントが寄せられました。

Reactが開発用に起こしているフェイクイベントは、レンダリングのためのもので、ユーザーイベントには関わらないといいます(該当コードはコメントのリンク参照)。

[Dan Abramov氏によるコメント(2018/02/03)](https://github.com/facebook/react/issues/12141#issuecomment-362737894)
> I don't believe it has to do with generating events fast. It has to do with *renders*.

> Because fake events are created every time we call into user code in DEV [here](https://github.com/facebook/react/blob/885a291141330eb74e8a98316e286a9ad093f22c/packages/shared/invokeGuardedCallback.js#L89).

Reactは開発モードで、レンダリングされるコンポーネントに、それぞれリスナーを割り当てます。フェイクイベントは、コンポーネントコードから例外スローを切り分けるためのものだということです。プロダクション版では、この処理は外されます。

[Dan Abramov氏によるコメント(2018/03/01)](https://github.com/facebook/react/issues/12141#issuecomment-385536273)
> As I mentioned before, it is expected that React will allocate a listener on every component render *in development mode*.

> We [use fake events](https://github.com/facebook/react/blob/7dd4ca2911d742f9b5c457ce2d010432fa2d6578/packages/shared/invokeGuardedCallback.js#L142-L147) to "isolate" component code so that if it throws, the browser displays an "Uncaught error" message even if some code above in the call stack accidentally catches it. Similarly this is what makes "break on all exceptions" work even though React is wrapping your components in a try/catch.

> This overhead doesn't exist in production versions.