Reactに関する知見を雑にまとめる用

Reactに関して、再勉強が必要だと感じたため
参考になった記事などをまとめるようのスクラップ

データのfetchやZustandで保持しているパラメータの加工などにuseEffectを使わなければならない状態の時があったのだが、適切だったのか検討する

前提:
Reactの関数は純関数であること
- 同じ入力には同じ出力であること (同じ入力が与えられた場合、コンポーネントは常に同じJSXを返す必要がある)
- トマトサラダを注文した人がオニオンサラダを受け取ってはいけない
- 自分の仕事に専念する (レンダー前に存在したオブジェクトや変数を変更しない)
- ある注文が他の誰かの注文を変更してはいけない
参考

一方で、現実的には副作用が必要になることもある
副作用とは
- 関数外に影響を与えること
- 引数以外の要因で結果が変わってしまうこと
また副作用という言葉は、Reactに限定された言葉ではないこと
参考

副作用を完全に排除することはできない

Reactの副作用を安全に扱うための方法は2つ提供されている
- イベントハンドラ: ユーザーのアクションが実行された時にReactが呼び出す関数
- useEffect: レンダリングに付随する副作用を扱うReactフック

useEffectは避難ハッチであることを理解する
避難ハッチ:
React の「外側に踏み出して」外部システムに接続するための避難ハッチ (escape hatch)
{中略}
アプリケーションのロジックとデータフローの大部分は、これらの機能に依存しないようにすべきです。

エフェクトは必要ないかもしれない
エフェクトが不要なケース
- レンダーのためのデータ変換にエフェクトは必要ない (propsのデータを加工するためのものなど)
- ユーザーイベントの処理にエフェクトは必要ない
https://ja.react.dev/learn/escape-hatches#you-might-not-need-an-effect
もっと詳しい内容。ここを読んで自社のコードの不要なコードは消したい

そもそもeffectとは?
前提: 副作用はレンダー中には実行してはいけない
ユーザーのアクションによって何かしらの副作用が生じる場合 -> イベントハンドラ
特定のイベントではなく、レンダー事態によって引き起こされる副作用を特定するため -> エフェクト
エフェクトを扱うためのReactフック -> useEffetc

useEffectはコンポーネントを外部システムと同期させるためのもの

外部システムとは?
Reactによって制御されていないもの
- ネットワーク
- 何らかのブラウザAPI
- サードパーティライブラリやウィジェット

エフェクトが行うこと
- 同期を開始する
- 同期を停止する
多くのケースで同期の停止も必要であること

詳細

コードをエフェクトに書くべきか、イベントハンドラに書くべきか悩んだ時に考えること
そのコードが実行される理由を自問する
コンポーネントがユーザに表示されるために実行されるべきコードにのみエフェクトを使用する

アプリケーションの初期化についてはなるほどって感じ
let didInit = false;
function App() {
useEffect(() => {
if (!didInit) {
didInit = true;
// ✅ Only runs once per app load
loadDataFromLocalStorage();
checkAuthToken();
}
}, []);
// ...
}

Reactのコードが画面に描画されるまでの話
トリガー -> レンダー -> コミット

- レンダーのトリガー(お客様の注文を厨房に伝える
- コンポーネントのレンダー(厨房で注文の品を提供する)
- DOMへのコミット(テーブルに注文の竹刀を提供する)

このUdemyで学んだ内容

このように書いて、
handoClickを実行してもageは1しか増加しない
const [age, setAge] = useState<number>(0)
const handleClick = () => {
setAge(age + 1)
setAge(age + 1)
setAge(age + 1)
}
理由: 最後の処理が完了したタイミングで、バッチ処理で更新され、再レンダリングされる

これであれば更新される
const [age, setAge] = useState<number>(0)
const handleClick = () => {
setAge((state) => state + 1)
setAge((state) => state + 1)
setAge((state) => state + 1)
}

コンソールに2回レンダリングされる理由
React StrictModeの場合だと呼び出される(開発中のみ)
・純関数である必要があること
・純関数であるかどうかをチェックするために2度レンダリングしている
Strice ModeをOnにしておくこと

ここの認識は最低限チームでも揃えておきたいところ
(Sortとかはやっちゃうよね)

onClick={handleClick}
のケースと
onClick={()=>handleClick}
の違い
- onClick={handleClick}
- コンポーネントが再レンダリングされても関数の再生性がおこなわれない
- onClick={()=>handleClick}
- コンポーネントが再レンダリングされると、関数の再生成がおこなわれる
- 引数を受け取る場合にはこっち
基本的にはどちらでも良いとのこと
引数がなければ、関数を渡して、引数があればアロー関数で描くのもありか

仮想DOMから実際のDOM(Real DOM)に更新するモデル図
(わかりやすい!)
更新前と更新後の仮想DOMを比較(Diff)し、差分を実際のDOMに反映させる(Patch)

Reactのレンダーとは
VDOMの差分チェックをすること
↓
差分分だけコミットする

useRef: 値を保持するフックス。useStateと異なるのは、値が更新されてもコンポーネントが再レンダリングされない
useStateなどでコンポーネントが再レンダリングされても、useRefの値は保持されている

余計なコンポーネントの再レンダリングを防ぐことができる
↓
DOMを操作するときに使用することが多い

React.memo(memoだけでもimportすれば使用可能)
子コンポーネントが親コンポーネントの再レンダリングによって、余計な再レンダリングされることを防ぐ(propsが変わらなければ、子コンポーネントが再レンダリングされない)
ただし、何でもかんでもmemo化するのは得策ではない
memo化を検討するケース
- 画面レンダリングが高負荷であり、画面がちらつくなどパフォーマンスに影響があるケース
- 例: D&D で画像を移動させるFigmaのようなもの
- 親コンポーネントが再レンダリングされることで、子コンポーネントのPropsが変換しない場合
- 子コンポーネントへのPropsが変化する場合には、memo化してもあまり効果が得られない

useCallBackを含むパフォーマンスチューニングについては基本的には最初は実施が不要
検討するケースとしては、
- アプリケーションのFE側の処理のパフォーマンスに影響が出た
- auth情報など、様々な画面で使用するケースが想定されるusecase関数
などがあるときには使用を検討する

useCallBack
再レンダリングによって、親コンポーネントから子コンポーネントに関数をpropsで渡している場合、関数が再生成されることで、子コンポーネントも再レンダリングされる

useCallBack: 関数の再生成を防ぐ
useMemo: 値の再計算を防ぐ

思想によるが、
カスタムフックスには、無条件でuseCallBackやuseMemoを使用しても良いのではとのこと

カスタムフックスでのuesCallbackについては公式ドキュメントでも言及されている

また、useEffectとuseCallBackの相補uow使用したリファクタリングについても記載がある