2020-07-08
やりたいこと
授業
日報
reselectとreact-reduxのコードを眺めていたら昼になっていた
reselect
やってることがuseMemo的な直前の値だけを持つメモ化なので,createSelectorでstate以外に引数を取るselector(reselectではParametizedSelectorと呼んでいるらしい)を定義すると,同時に表示されるコンポーネントがこのselectorを引数違いで呼んでいる場合にメモ化が意味をなさなくなってしまう
解決策として,param => Selectorみたいな関数で引数を取るSelectorを表現するようにして,paramの部分をlodashのmemoizeを使ってメモ化してやるといいっぽい
atomFamily/selectorFamily導入以前のrecoilっぽい
react-reduxというかuseSelector
他のhooksは別にどうってことないので
「どうせuseStateのinitialValueをgetStateから計算して後はuseEffectでsubscribeして更新するんだろう」って思っていたが,見事に裏切られた
SelectorとStateとSelectされたStateをrefに入れて管理し,useLayoutEffectの中では更新検知だけしてuseReducerを使ったハックでrerenderさせて,返す値は常に同期的にselectorを使って計算している
よく考えると,useEffectとかで更新検知をしたタイミングとrenderの間でdispatchが起きてStateが変化している可能性がある
更新検知のために常に前回のpropで作られたselectorが使われるので
これに対応するためにこういう実装になっているらしい,賢い……
React Concurrent Modeとかだとさらにrenderのタイミングと実際にDOMに反映されるタイミングが合わない(同じmicrotaskにならない)可能性があって,同じStateを参照しているはずなのに値が合わないという現象が発生することがある
この現象をTearingと呼んでいるらしい
この問題を解決するためにreact-redux v6ではContextにStateを入れるというアプローチが取られたが,巨大なStateの一部分でも変更されれば全てのコンポーネントがrerenderされてしまうという問題があって絶不評だったのは知られている