Closed10

『useEffect完全ガイド』を読む会

あけのあけの

README

読むもの

https://overreacted.io/ja/a-complete-guide-to-useeffect/

目的

useEffectを完全に理解する。
その過程でReact hooksとライフサイクルについても基礎から理解する。

進め方

参加者が交代で読む。
引っかかった点を話し合ったりコードを書いたりして完全理解してから進む。
どこまで読む等は決めずに時間内(1時間)で読めるところまでを読む。

作業リポジトリ

https://github.com/shzawa/use-effect-perfect-guide-study

Scrapbox

https://scrapbox.io/react-hooks-study/

あけのあけの

2022-04-11(月) 19:30-20:30

実績

  • 最初から
  • TLDR
    • 🤔 componentDidMountをuseEffectで再現する方法は?まで

次はどこから?

  • TLDR
    • 🤔 useEffect 内で正確に非同期処理を行う方法とは? [] ってなに? から

https://overreacted.io/ja/a-complete-guide-to-useeffect/#:~:text=🤔 useEffect 内で正確に非同期処理を行う方法とは? [] ってなに?

MEMO

この記事は useEffect API をある程度理解していることが前提です

理解しているか怪しかったので理解していきたい。

componentDidMountをuseEffectで再現する方法は?

Reactのライフサイクル(クラス/関数コンポーネント両方)の理解が曖昧。

あけのあけの

2022-04-18(月) 20:30-21:30

実績

  • TLDR
    • 🤔 useEffect 内で正確に非同期処理を行う方法とは? [] ってなに? から
  • 🤔 useEffect 内で正確に非同期処理を行う方法とは? [] ってなに?の終わりまで

次はどこから?

  • TLDR
    • 🤔 関数をエフェクトの依存関係として記すべき?から

https://overreacted.io/ja/a-complete-guide-to-useeffect/#:~:text=があります。-,🤔 関数をエフェクトの依存関係として記すべき?,-推薦される

MEMO

useEffect 内で正確に非同期処理を行う方法とは? [] ってなに?
この記事を参考にしてみると良いでしょう。最後まで読むように!

読みました。
https://www.robinwieruch.de/react-hooks-fetch-data/

学んだ部分をモブプロ
useEffect内でsleepすることで、return実行やsleep後のプログラムがどのように動いているかを見る。
https://github.com/shzawa/use-effect-perfect-guide-study/blob/e113a40149b4e325f5467cb5c8b47e40c8b48001/components/DidCancel.tsx

本題ではなかったがpromiseをcatchする方法について話した
https://qiita.com/akameco/items/cc73afcdb5ac5d0774bc

あけのあけの

2022-04-25(月) 20:00-21:00

実績

  • TLDR
    • 🤔 useEffect 内で正確に非同期処理を行う方法とは? [] ってなに? から
  • それぞれの render は独自の props と state を保持している
  • それぞれの render は独自のイベントハンドラを保持している まで

次はどこから?

  • それぞれの render は独自のエフェクトを保持しているから

https://overreacted.io/ja/a-complete-guide-to-useeffect/#それぞれの-render-は独自のエフェクトを保持している

MEMO

関数をエフェクトの依存関係として記すべき?

関数がpropsやstateを見ることができるのでuseCallbackを使って記すべき。
エフェクト内でしか使わないのであればエフェクト内で定義することでも解決する。
exhaustive-depsで大体解決する。
https://www.npmjs.com/package/eslint-plugin-react-hooks

非同期処理の無限ループがたまに起こるのはなぜ?

依存関係がおかしくなっているので見直しましょう。
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がどう動くかを書いた
https://github.com/shzawa/use-effect-perfect-guide-study/blob/e113a40149b4e325f5467cb5c8b47e40c8b48001/components/Counter.tsx

通常の関数

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のバージョン
https://zenn.dev/kata_n/articles/87e7b3d644c6cc

あけのあけの

2022-05-16(月) 20:00-21:00

実績

  • それぞれの render は独自のエフェクトを保持している から
  • それぞれの render は独自のエフェクトを保持している の終わりまで

間が空いたのでいままでの振り返りも含めつつ

次はどこから?

  • それぞれの render は全てを保持している から

https://overreacted.io/ja/a-complete-guide-to-useeffect/#それぞれの-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時の呼び出しがエラーとなっていた模様。
https://qiita.com/syu_ikeda/items/ea1e6931643aa812e6a2
https://github.com/shzawa/use-effect-perfect-guide-study/blob/e113a40149b4e325f5467cb5c8b47e40c8b48001/components/Counter.tsx#L57-L93

あけのあけの

2022-05-23(月) 20:00-21:00

実績

  • それぞれの render は全てを保持しているから
  • 流れに逆らう
  • では Cleanup はどうでしょう?まで

次はどこから?

  • ライフサイクルではなく、シンクロから

https://overreacted.io/ja/a-complete-guide-to-useeffect/#ライフサイクルではなく、シンクロ

MEMO

それぞれの render は全てを保持している

素直に記述した際の関数コンポーネントとクラスコンポーネントの挙動は異なる。
関数コンポーネントはrender実行時の値を表示する。
クラスコンポーネントはstate参照時の最新の値を表示する。

関数コンポーネント
https://github.com/shzawa/use-effect-perfect-guide-study/blob/71b1a883cc828bae7bfa01cc6606ff899437f4bc/components/Counter.tsx#L95-L111

クラスコンポーネント
https://github.com/shzawa/use-effect-perfect-guide-study/blob/71b1a883cc828bae7bfa01cc6606ff899437f4bc/components/Counter.tsx#L113-L139

varとletで挙動が異なる例
https://stackoverflow.com/questions/5226285/settimeout-in-for-loop-does-not-print-consecutive-values

関数の中で値をキャプチャすることでクラスコンポーネントでも関数コンポーネントと同じ挙動を再現できる。

クラスコンポーネント初めて書いた。
ComponentクラスがstateやsetStateを持っていて、それを使って更新をかける仕組みに見える。
State管理が大変そうなのでReact+Reduxというセットで語られることが多かったのかなという話をした。

流れに逆らう

useRef()を使って特定のrender内ではなく最新の値を取得できる。
classComponentでは自然に出来たことがuseRef()を使わないと出来ないようになっている。

https://github.com/shzawa/use-effect-perfect-guide-study/blob/71b1a883cc828bae7bfa01cc6606ff899437f4bc/components/Counter.tsx#L141-L162

ですが、 未来の props や state を読みたいということは React の流れに逆らっているというのを用心してください。

用心するべき部分を分かりやすくする役割も持っている。

letではなくconstを使うように、useRef()もmutate可能なので使いたくはないよねという話をした。
業務で使うことはあるが最初に代入してからは再代入しないことが大半。
useRefとReact18の相性の悪さについて
https://qiita.com/uhyo/items/6a3b14950c1ef6974024

では Cleanup はどうでしょう?

ライフサイクルのところでも出てきたCleanup関数の呼ばれる順番
https://zenn.dev/yodaka/articles/7c3dca006eba7d#更新-2

直感

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()の内部に踏み込んでいて良かった。
標準でメモ化していない理由もある程度納得できる。
https://zenn.dev/taroro28/articles/3bec0f3f4711e9

あけのあけの

2022-06-06(月) 20:00-21:00

実績

  • ライフサイクルではなく、シンクロから
  • React にエフェクトを比較することを教える
  • React に依存関係の嘘をついてはならない
  • 依存関係に嘘をつくと何が起こるのか
  • 依存関係に正直になる二つの方法
  • 自律的なエフェクトを作る
  • 関数アップデートと Google Docs
  • アクションからアップデートを分離するまで

復習も多かったのでスムーズに進んだ。

次はどこから?

  • なぜ useReducer は React Hooks のチートモードなのかから

チートモード、気になる。

https://overreacted.io/ja/a-complete-guide-to-useeffect/#なぜ-usereducer-は-react-hooks-のチートモードなのか

MEMO

ライフサイクルではなく、シンクロ

全ては結果であり、過程ではありません

宣言的UIという理解。
propsstateに応じてDOMをシンクロ(≒同期)する。
処理の過程によって結果が変動することはなく、最終結果は同じになるはず。
けれども全てのeffectをrender毎に実行したくないのでどうするか?

React にエフェクトを比較することを教える

ReactはDOMの変更された箇所のみをアップデートする。
effectの違いは一度関数が呼ばれるまでReactには分からないため、依存配列(deps)を渡す必要がある。
depsの比較をして、異なる場合はeffectが実行される。

depsの比較はReact hooksにおいては浅い比較。

ライブラリによっては深い比較になる場合もある。
https://swr.vercel.app/ja/docs/arguments#オブジェクトの受け渡し
SWRはマイナーバージョンが変わった際にそこの仕様が変更されて若干沼った。
破壊的変更はメジャーバージョンでやってよ…

React に依存関係の嘘をついてはならない

depsにはeffectで使われる値全てを指定する必要がある。
depsから特定の値を削除することで実行タイミングを制御することは避けるべき。

依存関係に嘘をつくと何が起こるのか

一度しかインクリメントしない例
count = 0なので常にsetCount(0 + 1)が呼び出される。
https://github.com/shzawa/use-effect-perfect-guide-study/blob/be05ad1b85dfe0c8c206e79770f333c4f8af58ff/components/Counter.tsx#L164-L175

デフォルトで入っているだろうhooksのlint rule
https://www.npmjs.com/package/eslint-plugin-react-hooks

依存関係に正直になる二つの方法

どうやって解決するのか?

  • 素直にdepsに入れる
  • よく変更される値を必要としないようにエフェクトを書き変える→自律的なエフェクトを作る

自律的なエフェクトを作る

関数型の更新
インクリメントする処理を渡す
Reactがstateを知っている
https://github.com/shzawa/use-effect-perfect-guide-study/blob/be05ad1b85dfe0c8c206e79770f333c4f8af58ff/components/Counter.tsx#L177-L188

関数アップデートと Google Docs

急なGoogle Docsで困惑したが、例として出してきただけらしい。
この2つはユーザーの動き、差分を送ってバッチ適用される点で似ている。
複雑なアップデートがしたい場合はuseReducerが便利な手段となる。

アクションからアップデートを分離する

useReducerの導入。

もし変数がもう一つの変数の現在値に依存してしまっている場合は、それらを useReducer に置き換えた方がいいでしょう。

countstepをuseReducerで同時に管理して、dispatchされたアクションをreducerで処理する。

その他

Module内の関数をjestでどうmockするか
https://medium.com/welldone-software/jest-how-to-mock-a-function-call-inside-a-module-21c05c57a39f
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 を子に渡して親コンポーネントのエンキャプスレーションを壊す行為(そして数週間後になぜやる必要があったのかに気づく)に慣れてしまいました。

から。

https://overreacted.io/ja/a-complete-guide-to-useeffect/#:~:text=// ... } }-,React に関わって数年,-、もう不必要な

エンキャプスレーションとは…?

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回程度に抑えたい。

子コンポーネントに関数を渡すときにメモ化したほうがいいというのはこういうこと。

関数はデータフローの一部なのか

クラスコンポーネントわからない問題。
componentDidUpdateprevProps(前回の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()

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
完璧な挙動の理解には至っていなかったので読んだ。(私は使ったことなかった)
関数のthisを定義するもの。インスタンス化。
ついでに引数も渡すことができる。

Cの関数ポインタを思い出した。
実行時にthisを差し替えることが関数ポインタの指す場所を変更して動作を変えるのに近い黒魔術を感じたため。

その他

スムーズに行けば来週で読み終わりそうなので、次何読もうかという話。
他では

あたりを読んでいる。

https://zenn.dev/morinokami/books/learning-patterns-1
これを読みたいという流れになったので第一候補としておく。
現代のフロントエンド設計で汎用的に使えそうな知識。
各章のタイトルを見るとGoFのデザインパターンっぽいものもある。
字数が多いので4ヶ月程度はかかるかもしれない…
デザインパターンを個々人の経験に当てはめて雑談を交えつつ進めていきたい。

あけのあけの

2022-06-27(月) 20:00-21:00

実績

  • 関数はデータフローの一部なのかの途中から
  • レースコンディションについてまで

次はどこから?

  • ハードルをあげるから

https://overreacted.io/ja/a-complete-guide-to-useeffect/#ハードルをあげる

次回こそ最終回。

MEMO

関数はデータフローの一部なのか

エンキャプスレーション(encapsulation)->カプセル化
データフローとは?
stateがrender毎に定まるのと同様、関数の出力も定まることがデータフローの一部であると理解した。
関数がデータの流れに乗って変化している。
useCallbackによって依存の変更で関数の変更が起こることでそれを実現している。

useMemo
第一引数の返り値をcache的に保持する。
オブジェクトの再生成が抑えられるので、浅い比較が通るようになる。
主にオブジェクトを子コンポーネントにわたす際や重い処理を毎render走らさないために使われる。

個人的にはuseContextは依存関係の見通しが悪くなって好きではない…

レースコンディションについて

useEffectに非同期関数を渡すと警告が出る。
競合状態(レースコンディション)となる可能性があるため。
最新の処理結果を後から完了した過去の処理結果で上書きすることが起こりうる。

あるいは、boolean を用いてトラッキングするというその場しのぎの解決方法もあります
https://www.robinwieruch.de/react-hooks-fetch-data/

過去の回で読んだ記事。
sleepを用いてuseEffect内で非同期処理(async/await)を使った際の挙動について。
主にcleanupのタイミングや関数内のローカル変数の結びつきについて復習した。

https://github.com/shzawa/use-effect-perfect-guide-study/blob/e113a40149b4e325f5467cb5c8b47e40c8b48001/components/DidCancel.tsx

「useEffect内の関数は各render毎に独立している(render毎にstateが定数みたいになるのと同じ)」と理解した。
どちらかというと「関数なのだから複数回実行してもローカル変数が実行を跨いで共有されない」と言ったほうが正しい気がした。

次回の話

具体的なコードは残っておらず、まとめと感想会になりそう。
次に何を読むかも含めて決めていけるといい。

あけのあけの

2022-07-04(月) 20:00-21:00

実績

  • ハードルをあげるから

次はどこから?

読了。

MEMO

ハードルをあげる

レンダリングと同期させるのはバグを減らすが、書くのが難しくなっている。
useEffectは低レイヤーなhooksなのでより高レイヤーなhooksに置き換わっていく。
データフェッチングライブラリやSuspenseが使われるようになっている。

https://zenn.dev/kazuma1989/articles/a30ba6e29b5b4c

時間が余ったので途中まで読んだ。
https://zenn.dev/uhyo/books/react-concurrent-handson

終わりに

TL;DRの振り返り。
クラスコンポーネントやuseEffectの挙動、Reactのライフサイクルについての理解が深まった。

読後の感想

useEffectもそうだが、Reactのレンダリング周りの処理順について関連ドキュメントを参照することが多かた。
開発中は分からないことが出てきた際にドキュメントを当たることが多いので、一通りさらってみると新たな発見がありそう。
こちらの新しいドキュメントも気になっている。
https://beta.reactjs.org/

useEffectは万能なので、出来るだけ他の手段で代替しつつ本当に必要な箇所に絞って使っていければ良い。
用途に応じた高レイヤーなhooksを用いていきつつ、使用する際は依存関係を把握できているか・よりシンプルに出来ないかを考えていきたい。

その他

お疲れさまでした。
次回からはこちらを読んでいく予定。
https://zenn.dev/morinokami/books/learning-patterns-1

このスクラップは2022/07/05にクローズされました