『useEffect完全ガイド』を読む会
README
読むもの
目的
useEffectを完全に理解する。
その過程でReact hooksとライフサイクルについても基礎から理解する。
進め方
参加者が交代で読む。
引っかかった点を話し合ったりコードを書いたりして完全理解してから進む。
どこまで読む等は決めずに時間内(1時間)で読めるところまでを読む。
作業リポジトリ
Scrapbox
2022-04-11(月) 19:30-20:30
実績
- 最初から
- TLDR
-
🤔 componentDidMountをuseEffectで再現する方法は?
まで
-
次はどこから?
- TLDR
-
🤔 useEffect 内で正確に非同期処理を行う方法とは? [] ってなに?
から
-
MEMO
この記事は useEffect API をある程度理解していることが前提です
理解しているか怪しかったので理解していきたい。
componentDidMountをuseEffectで再現する方法は?
Reactのライフサイクル(クラス/関数コンポーネント両方)の理解が曖昧。
-
hooksの公式を読んだ
https://ja.reactjs.org/docs/hooks-effect.html -
ライフサイクルの理解で記事を読んだ
https://zenn.dev/yodaka/articles/7c3dca006eba7d
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
https://qiita.com/kawachi/items/092bfc281f88e3a6e456 -
Scrapboxを使った各々の理解のまとめ
https://scrapbox.io/projects/react-hooks-study/invitations/1ca9690d3fa84a4662b37b6e21327791
2022-04-18(月) 20:30-21:30
実績
- TLDR
-
🤔 useEffect 内で正確に非同期処理を行う方法とは? [] ってなに?
から
-
-
🤔 useEffect 内で正確に非同期処理を行う方法とは? [] ってなに?
の終わりまで
次はどこから?
- TLDR
-
🤔 関数をエフェクトの依存関係として記すべき?
から
-
MEMO
- useEffectの挙動の復習も兼ねて読んだ
https://zenn.dev/taroro28/articles/49e95f01bab7ae
useEffect 内で正確に非同期処理を行う方法とは? [] ってなに?
この記事を参考にしてみると良いでしょう。最後まで読むように!
読みました。
学んだ部分をモブプロ
useEffect内でsleepすることで、return実行やsleep後のプログラムがどのように動いているかを見る。
- console.logデバッグをすると初回Renderが2回走っていそう
yarn dev
(開発モード)では2回実行される
yarn build && yarn start
では想定通りの動きをした
https://stackoverflow.com/questions/48846289/why-is-my-react-component-is-rendering-twice
本題ではなかったがpromiseをcatchする方法について話した
2022-04-25(月) 20:00-21:00
実績
- TLDR
-
🤔 useEffect 内で正確に非同期処理を行う方法とは? [] ってなに?
から
-
それぞれの render は独自の props と state を保持している
-
それぞれの render は独自のイベントハンドラを保持している
まで
次はどこから?
-
それぞれの render は独自のエフェクトを保持している
から
MEMO
関数をエフェクトの依存関係として記すべき?
関数がpropsやstateを見ることができるのでuseCallbackを使って記すべき。
エフェクト内でしか使わないのであればエフェクト内で定義することでも解決する。
exhaustive-deps
で大体解決する。
-
ホイスティング
https://developer.mozilla.org/ja/docs/Glossary/Hoisting
https://zenn.dev/oreo2990/articles/283437e3bc9f7d
結局あいまいな理解になった… -
関数コンポーネントとクラスコンポーネント
ライフサイクルのところでなんとなく理解した部分の補完
https://overreacted.io/ja/how-are-function-components-different-from-classes/
非同期処理の無限ループがたまに起こるのはなぜ?
依存関係がおかしくなっているので見直しましょう。
useMemoやuseCallbackが役立つ。
古い state か props がエフェクト内にたまに入るのはなぜ?
exhaustive-deps
使って。
それぞれの render は独自の props と state を保持している
useStateはrenderごとに異なる値をconst変数として保持していることと同じ。
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
// 初期 render 時
function Counter() {
const count = 0; // useState()の戻り値
// ...
<p>You clicked {count} times</p>;
// ...
}
// クリック後、関数が呼び出される
function Counter() {
const count = 1; // useState()の戻り値
// ...
<p>You clicked {count} times</p>;
// ...
}
// もう一度クリックすると、再度呼ばれる
function Counter() {
const count = 2; // useState()の戻り値
// ...
<p>You clicked {count} times</p>;
// ...
}
それぞれの render は独自のイベントハンドラを保持している
ここまでは順調ですね。
順調かもしれない…
Counterがどう動くかを書いた
通常の関数
function sayHi(person) {
const name = person.name;
setTimeout(() => {
alert("こんにちは, " + name);
}, 3000);
}
let someone = { name: "Dan" };
sayHi(someone);
someone = { name: "Yuzhi" };
sayHi(someone);
someone = { name: "Dominic" };
sayHi(someone);
const name = person.name;
がなくてオブジェクトの直接参照だったら、オブジェクトの中身を書き換えた場合表示が変わるよねという話をした。
structuredClone lodash.cloneDeep() が不要になるかもしれない
問題はiOS/Safariのバージョン
2022-05-16(月) 20:00-21:00
実績
-
それぞれの render は独自のエフェクトを保持している
から -
それぞれの render は独自のエフェクトを保持している
の終わりまで
間が空いたのでいままでの振り返りも含めつつ
次はどこから?
-
それぞれの render は全てを保持している
から
MEMO
それぞれの render は独自のエフェクトを保持している
useEffectの依存配列を[]
にした際にtitleが変わらないという挙動になるはず。
ただYou clicked {count} times
の部分が変わると勘違いしていてちょっと詰まった。
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
React/あなたのコンポーネント/ブラウザ の掛け合いが復習を促してきた。
わかりやすいかもしれない…?
その他
useEffectの外でdocumentが使えなかったので調べた。
SSR時の呼び出しがエラーとなっていた模様。
2022-05-23(月) 20:00-21:00
実績
-
それぞれの render は全てを保持している
から 流れに逆らう
-
では Cleanup はどうでしょう?
まで
次はどこから?
-
ライフサイクルではなく、シンクロ
から
MEMO
それぞれの render は全てを保持している
素直に記述した際の関数コンポーネントとクラスコンポーネントの挙動は異なる。
関数コンポーネントはrender実行時の値を表示する。
クラスコンポーネントはstate参照時の最新の値を表示する。
関数コンポーネント
クラスコンポーネント
varとletで挙動が異なる例
関数の中で値をキャプチャすることでクラスコンポーネントでも関数コンポーネントと同じ挙動を再現できる。
クラスコンポーネント初めて書いた。
ComponentクラスがstateやsetStateを持っていて、それを使って更新をかける仕組みに見える。
State管理が大変そうなのでReact+Reduxというセットで語られることが多かったのかなという話をした。
流れに逆らう
useRef()
を使って特定のrender内ではなく最新の値を取得できる。
classComponentでは自然に出来たことがuseRef()
を使わないと出来ないようになっている。
ですが、 未来の props や state を読みたいということは React の流れに逆らっているというのを用心してください。
用心するべき部分を分かりやすくする役割も持っている。
let
ではなくconst
を使うように、useRef()
もmutate可能なので使いたくはないよねという話をした。
業務で使うことはあるが最初に代入してからは再代入しないことが大半。
useRefとReact18の相性の悪さについて
では Cleanup はどうでしょう?
ライフサイクルのところでも出てきたCleanup関数の呼ばれる順番
直感
React が {id: 10} のエフェクトを cleanup する。
React が {id: 20} の UI を render する。
React が {id: 20} のエフェクトを実行する。
現実
React が {id: 20} の UI を render する。
ブラウザが描画する。{id: 20} の時の UI が画面に表示される。
React が {id: 10} のエフェクトを cleanup する。
React が {id: 20} のエフェクトを実行する。
React はブラウザが描画した後に初めてエフェクトを実行します。この方法だとスクリーンアップデートをブロックすることがないので、アプリを早くしてくれます。それと同様で、エフェクトの cleanup も遅れて実行されます。 前のエフェクトは新しい props で re-render されてから cleanup されます:
帝国は滅び遺灰に変わり、太陽の外層は削ぎ落とされ白色矮星に変形し、最後の文明は終わりを迎えます。ですが誰も初期 render の cleanup を定義された特定の render 内の {id: 10} 以外のものを cleanup させることはできません。
Reactの仕組みと今までの学習(それぞれの render は全てを保持している
)の合わせ技。
その他
React18触る会やりたい。
React.memo()
の内部に踏み込んでいて良かった。
標準でメモ化していない理由もある程度納得できる。
2022-06-06(月) 20:00-21:00
実績
-
ライフサイクルではなく、シンクロ
から React にエフェクトを比較することを教える
React に依存関係の嘘をついてはならない
依存関係に嘘をつくと何が起こるのか
依存関係に正直になる二つの方法
自律的なエフェクトを作る
関数アップデートと Google Docs
-
アクションからアップデートを分離する
まで
復習も多かったのでスムーズに進んだ。
次はどこから?
-
なぜ useReducer は React Hooks のチートモードなのか
から
チートモード、気になる。
MEMO
ライフサイクルではなく、シンクロ
全ては結果であり、過程ではありません
宣言的UIという理解。
props
とstate
に応じてDOMをシンクロ(≒同期)
する。
処理の過程によって結果が変動することはなく、最終結果は同じになるはず。
けれども全てのeffectをrender毎に実行したくないのでどうするか?
React にエフェクトを比較することを教える
ReactはDOMの変更された箇所のみをアップデートする。
effectの違いは一度関数が呼ばれるまでReactには分からないため、依存配列(deps)を渡す必要がある。
depsの比較をして、異なる場合はeffectが実行される。
depsの比較はReact hooksにおいては浅い比較。
ライブラリによっては深い比較になる場合もある。
破壊的変更はメジャーバージョンでやってよ…
React に依存関係の嘘をついてはならない
depsにはeffectで使われる値全てを指定する必要がある。
depsから特定の値を削除することで実行タイミングを制御することは避けるべき。
依存関係に嘘をつくと何が起こるのか
一度しかインクリメントしない例
count = 0
なので常にsetCount(0 + 1)
が呼び出される。
デフォルトで入っているだろうhooksのlint rule
依存関係に正直になる二つの方法
どうやって解決するのか?
- 素直にdepsに入れる
- よく変更される値を必要としないようにエフェクトを書き変える→自律的なエフェクトを作る
自律的なエフェクトを作る
関数型の更新
インクリメントする処理を渡す
Reactがstateを知っている
関数アップデートと Google Docs
急なGoogle Docsで困惑したが、例として出してきただけらしい。
この2つはユーザーの動き、差分を送ってバッチ適用される点で似ている。
複雑なアップデートがしたい場合はuseReducer
が便利な手段となる。
アクションからアップデートを分離する
useReducer
の導入。
もし変数がもう一つの変数の現在値に依存してしまっている場合は、それらを useReducer に置き換えた方がいいでしょう。
count
とstep
をuseReducerで同時に管理して、dispatchされたアクションをreducerで処理する。
その他
Module内の関数をjestでどうmockするか
A Sub-Optimal Solution Worth Mentioning — Exporting a Namespace Object のパターンが解決しやすそう。chrome拡張機能の開発が楽しそうという話。
data-testIdをつけたら該当のchildrenが消えるらしい…
2022-06-13(月) 20:00-21:00
実績
-
なぜ useReducer は React Hooks のチートモードなのか
から エフェクト内に関数を入れる
でも、この関数はエフェクト内に入れられない
-
関数はデータフローの一部なのか
の途中まで
次はどこから?
-
関数はデータフローの一部なのか
の途中
React に関わって数年、もう不必要な props を子に渡して親コンポーネントのエンキャプスレーションを壊す行為(そして数週間後になぜやる必要があったのかに気づく)に慣れてしまいました。
から。
エンキャプスレーションとは…?
MEMO
なぜ useReducer は React Hooks のチートモードなのか
reducer関数をcomponent内に入れるパターンはあまり使わない。
ただ、そうすると確かにpropsは参照できる…
この場合でも、 dispatch は再 render されても不変であることは保証されています。
dispatchは不変なので依存配列から取り除ける。
reducerは最新のpropsを参照できる。
チートモード=ロジックと宣言的な書き方の分離により、必要最低限のrenderが実現できる
エフェクト内に関数を入れる
linterに従っておかないと依存を管理するのは大変。
エフェクト内部でのみ使用するならエフェクト内に関数をいれる方法がシンプル。
でも、この関数はエフェクト内に入れられない
複数のエフェクトで同じ関数を使いたい場合は?
- コンポーネント外に書く
ホイスティングがまた出てきた。宣言を巻き上げている。
renderごとに再定義されることはなくなる。 - useCallbackを使う
https://ja.reactjs.org/docs/hooks-reference.html#usecallback
関数の依存しているものを管理して、関数の再生成タイミングをrenderごとではなく依存の変更ごとにする。
関数が変更されるとそれに依存しているuseEffectも実行される。
数珠繋ぎで依存が繋がっていくのがプロダクトコードを書くときは可読性が低くなって辛い。
ネストもなく平坦なので読み解くのが難しくなっている。
出来ればuseEffect使わずに済ませたいし、使うにしても依存の連鎖は1回程度に抑えたい。
子コンポーネントに関数を渡すときにメモ化したほうがいいというのはこういうこと。
関数はデータフローの一部なのか
クラスコンポーネントわからない問題。
componentDidUpdate
はprevProps
(前回のrender時のprops)を受け取ることが出来る。
今のfetchData
と前のfetchData
が異なるならデータ取得をしたいが、クラスなのでfetchData
が常に同一となってしまいfetchされない。
componentDidUpdate(prevProps) {
// 🔴 この比較は正にはならない
if (this.props.fetchData !== prevProps.fetchData) {
this.props.fetchData();
}
}
こちらはrender毎にfetchされる。
componentDidUpdate(prevProps) {
this.props.fetchData();
}
親で特定のクエリにbindしてもthis.props.fetchData !== prevProps.fetchData
は常に正。== 常に異なるfetchData
が入ってきてrender毎にfetchされる。
おそらくbindが毎回新しい関数を生成して返しているため。(よくわかってない)
render() {
return <Child fetchData={this.fetchData.bind(this, this.state.query)} />;
}
これらを解決するにはthis.state.query
をchildにpropsとして渡す必要があるが、childで使われることのないquery
を渡すのはあまりやりたくなかった。
Function.prototype.bind()
関数のthisを定義するもの。インスタンス化。
ついでに引数も渡すことができる。
Cの関数ポインタを思い出した。
実行時にthisを差し替えることが関数ポインタの指す場所を変更して動作を変えるのに近い黒魔術を感じたため。
その他
スムーズに行けば来週で読み終わりそうなので、次何読もうかという話。
他では
- https://zenn.dev/gunners6518/books/4c4672f32dd100
- https://zenn.dev/tkdn/books/react-testing-patterns
あたりを読んでいる。
現代のフロントエンド設計で汎用的に使えそうな知識。
各章のタイトルを見るとGoFのデザインパターンっぽいものもある。
字数が多いので4ヶ月程度はかかるかもしれない…
デザインパターンを個々人の経験に当てはめて雑談を交えつつ進めていきたい。
2022-06-27(月) 20:00-21:00
実績
-
関数はデータフローの一部なのか
の途中から -
レースコンディションについて
まで
次はどこから?
-
ハードルをあげる
から
次回こそ最終回。
MEMO
関数はデータフローの一部なのか
エンキャプスレーション(encapsulation)
->カプセル化
データフロー
とは?
stateがrender毎に定まるのと同様、関数の出力も定まることがデータフローの一部であると理解した。
関数がデータの流れに乗って変化している。
useCallback
によって依存の変更で関数の変更が起こることでそれを実現している。
useMemo
第一引数の返り値をcache的に保持する。
オブジェクトの再生成が抑えられるので、浅い比較が通るようになる。
主にオブジェクトを子コンポーネントにわたす際や重い処理を毎render走らさないために使われる。
-
https://ja.reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down
callbackを複数個渡すことの回避策→useReducer
バケツリレーの回避策→useContext
個人的にはuseContextは依存関係の見通しが悪くなって好きではない…
レースコンディションについて
useEffectに非同期関数を渡すと警告が出る。
競合状態(レースコンディション)となる可能性があるため。
最新の処理結果を後から完了した過去の処理結果で上書きすることが起こりうる。
あるいは、boolean を用いてトラッキングするというその場しのぎの解決方法もあります
https://www.robinwieruch.de/react-hooks-fetch-data/
過去の回で読んだ記事。
sleepを用いてuseEffect内で非同期処理(async/await)を使った際の挙動について。
主にcleanupのタイミングや関数内のローカル変数の結びつきについて復習した。
「useEffect内の関数は各render毎に独立している(render毎にstateが定数みたいになるのと同じ)」と理解した。
どちらかというと「関数なのだから複数回実行してもローカル変数が実行を跨いで共有されない」と言ったほうが正しい気がした。
次回の話
具体的なコードは残っておらず、まとめと感想会になりそう。
次に何を読むかも含めて決めていけるといい。
2022-07-04(月) 20:00-21:00
実績
-
ハードルをあげる
から
次はどこから?
読了。
MEMO
ハードルをあげる
レンダリングと同期させるのはバグを減らすが、書くのが難しくなっている。
useEffect
は低レイヤーなhooksなのでより高レイヤーなhooksに置き換わっていく。
データフェッチングライブラリやSuspenseが使われるようになっている。
時間が余ったので途中まで読んだ。
終わりに
TL;DRの振り返り。
クラスコンポーネントやuseEffectの挙動、Reactのライフサイクルについての理解が深まった。
読後の感想
useEffectもそうだが、Reactのレンダリング周りの処理順について関連ドキュメントを参照することが多かた。
開発中は分からないことが出てきた際にドキュメントを当たることが多いので、一通りさらってみると新たな発見がありそう。
こちらの新しいドキュメントも気になっている。
useEffectは万能なので、出来るだけ他の手段で代替しつつ本当に必要な箇所に絞って使っていければ良い。
用途に応じた高レイヤーなhooksを用いていきつつ、使用する際は依存関係を把握できているか・よりシンプルに出来ないかを考えていきたい。
その他
お疲れさまでした。
次回からはこちらを読んでいく予定。