守秘義務のため記載してあるソースコードは問題が起きたものを再現したもので,そのものではありません。
=
社員さんからはこの記事を書くための了承は得ています。
何が起こったの??
これはインターン中に起こった悲劇です。
自分のアサインされているプロジェクトでは Preact
と呼ばれる 軽量化版React のようなものを使って開発を行なっています。機能面で言えば些細な違いはありますがほぼほぼ React
です。
そこで以下のようなJSXを書きました。
<div onClick={props.switchToggle(props.list)}>
<Notification />
</div>
この this.props.switchToggle
と呼ばれるメソッドは Unistore
と呼ばれる redux
のような状態管理ツールの state をいじっています。
これを実行したところ、なぜかブラウザ超重くなり動かなくなっちゃいました。
原因を究明するために this.props.switchToggle
内に console.log("test")
を記載するとおびただしい量の test という文字列が出力され続けました。
この挙動を見る限り「無限ループ」起きてるじゃんということに気づきました。
メソッド内に問題があるか否かを検証するために console.log("test")
で置き換える。
<div onClick={console.log("test")}>
<Notification />
</div>
その結果、レンダリング時にクリックしていないのにも関わらず test
が表示されました。。
訳がわかりません。
そこでダメ元でアロー演算子を使ってみた..。
<div onClick={() => console.log("test")}>
<Notification />
</div>
すると正常に動く...。
なので、最初の構文もアロー演算子で実装すると...。
<div onClick={() => props.switchToggle(props.list)}>
<Notification />
</div>
これも正常に動く...。
ただ、自分たちのプロジェクトだとJSX内にアロー関数を書くと eslint error になるので代案が必要です。
まず、解決の前にこの原因をとりあえずこの状態で色々試したみた所、
「JSX内の関数に引数をつけると即時関数になっているのでは」
という結論に至りました。
これを解決する為、もともと functional Component
で作成したいましたが,それを Class Compoent
に置き換えて以下のようなメソッドを定義。
class Header extends Component {
switchToggle = () => this.props.switchToggle(this.props.list);
こうすることでJSX内では引数なしで関数を呼び出すことができます。
こんな感じです。
class Header extends Component {
switchToggle = () => this.props.switchToggle(this.props.list);
render() {
return (
<div onClick={this.switchToggle}>
<Notification />
</div>
);
}
}
結局,何が起こっていたのか??
この記事を書くにあたって色々調べていたら公式でも説明がありました。
https://reactjs.org/docs/handling-events.html#passing-arguments-to-event-handlers
要約すると関数に引数を渡すとレンダリング時にそのまま実行しちゃうので アロー関数 を使うか bind させるかする必要があるようです。
けど,それだと無限ループ起こらんくない??
確かに普通の関数をが即時実行されているのであれば起こりません。
ただ,今回の場合は即時実行されていたのは uniStore
の store
の状態をいじっていたメソッドです。
つまり
- コンポーネントがレンダリングされる
- 問題のメソッドが実行される
-
Store
内の値が書き換わる -
Store
内の値が変更されたのでそれに合わせて再レンダリングが走る - レンダリングされたので 2. に戻る
という感じでループが発生していたようです。
追記
自分は Class Component
を使用して実装しましたが他のパターンでの実装も教えてもらったので共有します!
functional Component
const Header = props => {
const { list, switchToggle } = props;
const handleClick = () => switchToggle(list);
return (
<div onClick={handleClick}>
<Notification />
</div>
);
}
Hooks
Preactはv10からHooksに対応します。
https://github.com/preactjs/preact/releases
const Header = ({ list, switchToggle }) => {
const toggle = useCallback(() => {
switchToggle(list);
}, [list, switchToggle]);
return (
<div onClick={toggle}>
<Notification />
</div>
);
}
最後に
あんまり知られていないであろう知識なので共有しました。
僕自身,このバグで1時間以上費やしたのでこの記事が同じ問題に遭遇した誰かの役に立てれば嬉しいです...。
自分もまだまだ勉強中の身なのでこの記事に技術的な間違いや誤字があるかもしれないので,その時は優しくコメントで指摘してもらえると幸いです。
それでは最後までお付き合いありがとうございました!!