Open23

React Fast Refreshを調べる

ピン留めされたアイテム
uttkuttk

タイトル通り、React Fast Refreshについて調べたことを追記して行きます💪

ある程度まとまったら、記事にする予定です📜

何かあれば、お気軽にコメントしてください💁‍♂️💁‍♀️

uttkuttk

こっちのライブラリも読む。

https://github.com/facebook/react/tree/master/packages/react-refresh

リンクカードからは分かりにくいが、上記のリンクはreact-refreshへのリンク。

uttkuttk

react-refresh

このパッケージは、Fast Refreshをバンドラーに統合するために必要な配線を実装します。Fast Refreshは、実行中のアプリケーションで状態を失うことなくReactコンポーネントを編集できるようにする機能です。これは「ホットリロード」と呼ばれる古い機能に似ていますが、Fast Refreshの方が信頼性が高く、Reactによって正式にサポートされています。

このパッケージは、主にバンドラープラグインの開発者を対象としています。作業中の場合は、このパッケージを使用したFastRefresh統合の大まかなガイドを次に示します。

uttkuttk

以下の動画をある程度理解する。

https://youtu.be/Mjrfb1r3XEM

uttkuttk

Fast Refreshとは?

  • ソースコードの変更を素早く反映させるための機能。
    • 動画では、React Nativeを用いて解説していた。
  • バンドルを使用してローカル環境で作業する事を想定している
  • webpackParcelMetroなどのバンドラーを使用する
    • Fast Refreshは、上記のバンドラーのプラグインを用いて実現している
  • プラグインは更新されたコードを検知し、それがReactコンポーネントかどうかを判断する
  • Reactコンポーネントであった場合、Reactが変更タイプ(種類)によってコンポーネントを更新する方法を決定する
  • ランタイムの更新(コンパイル後の更新)は、Reactを使用して更新をスケジュール(予約)して、新しいコードが表示されるようにする
uttkuttk

ホットリロードとの違いは?

  • 多くの点で両者は似ている
  • ではなぜ、Fast Refreshを作ったのか?
    • React Nativeのホットリロードにはいくつかの問題点があった
      • 関数コンポーネントをサポートして無かった( Hooksもサポート無し )
      • 構文エラーやランタイムエラーが出てもリロードされない
      • 変更後にリロードされないことが多い
      • 変更するたびにパッチやコードの変換が必要( 結果、重くなりやすい )
  • Fast Refreshがホットリロードと違う所
    • Fast Refreshの機能自体が、Reactに組み込まれている
    • Hooks付きの関数コンポーネントをサポート
    • リロードしてもStateを保持する
uttkuttk

どのようにしてFast Refreshは実現されているか?

  • この動画では、Hooksの変更について解説する

  • 用語について

    • update(更新)
      • Reactコンポーネントを再レンダリングし、State(状態)を維持する事
    • remount(再マウント)
      • 古いコンポーネントコードをアンマウントして、新しいバージョンのモノを再マウントする事
  • コードが変更された時、Fast Refreshはコンポーネントをupdateするかremountするかを決定する

  • 決定方法は、components signatureと言うモノを用いて計算して決める

  • components signatureはコンポーネントが使用するHooksを説明する文字列

  • 例えば、useState()useMemo()を使うコンポーネント場合は、以下のようなsignatureが含まれている可能性があるため、Fast Refreshによって計算される

動画より引用
// singnature: "useState{count} useMemo{val}"

function Example() {
  const [count, setCount] = useState(0);
  const val = useMemo(() => ..., [count]);

  // ...
}
  • なぜこのsignature文字列が役に立つのか?

    • コンポーネントの変更するたびにFast Refreshによって、このsignature文字列を再計算します
      • signatureが同じである場合、コンポーネントをupdate(更新)しても安全
      • signatureが変更された場合、コンポーネントをremount(再マウント)する
  • 例えば、useState()useReducer()に置き換えるとsignatureが変更されます

動画より引用
- // signature: "useState{count}"
+ // signature: "useReducer{state}"
function Example() {
- const [count, setCount] = useState(0);
+ const [state, dispatch] = useReducer(...);
  
  // ...
}
  • この場合、Fast Refreshはマウントを解除してremount(再マウント)する
    • Hooksの動作が異なっていてState(状態)を共有できないので、正しい挙動
uttkuttk

Custom Hooksについて

  • 内部の配列でHooksを管理しているため、fast refreshはCustom Hooksを認識する必要はありません。
  • 例えば、ここに二つのCustom Hooksがfetchを使用しているとします。
動画より引用
function useFetch(url) {
  const [value, setValue: = useState();

  useEffect(() => ...);

  // ...
}

function useSubscription(config) {
  const [value, setValue] = useState();

  useEffect(() => ...);

  // ...
}
  • どちらも組み込みのuseState()useEffect()を使用して、同じような反応をする。
  • しかし、それを考慮して状態を共有するのは恐らく安全ではない
  • そのため、fast refreshは作成する署名の一部としてCustom Hooks名を含めるようにしています。
    • 補足: 署名にはCustom Hooks名が含まれるが、Custom Hooksを監視していない事に注意!
動画より引用
// signature: "useFetch{} useState{value} useEffect{}"
function useFetch(url) {
  const [value, setValue: = useState();

  useEffect(() => ...);

  // ...
}

// signature: "useSubscription{} useState{value} useEffect{}"
function useSubscription(config) {
  const [value, setValue] = useState();

  useEffect(() => ...);

  // ...
}
  • 例えば、以下のようなコンポーネントから初めて、divの代わりにspanを返すように変更した場合、fast refreshでは更新時にHooksの状態が保持されますが、Custom Hooksを置き換えた場合には、変更された署名を確認して、新しいコンポーネントを新しい状態で再マウント(remount)します。
Hooksの状態が保持される例/動画より引用
// signature: "useFetch{value} useState{value} useEffect{}"
function Example() {
  const value = useFetch(...);

-  return <div>{value}</div>;
+  return <span>{value}</span>;
}
再マウント(remount)される例/動画より引用
- // signature: "useFetch{value} useState{value} useEffect{}"
+ // signature: "useSubscription{value} useState{value} useEffect{}"
function Example() {
-  const value = useFetch(...);
+  const value = useSubscription(...);

  return <span>{value}</span>;
}
  • つまり、署名はuseFetch()の状態が、useSubscription()に影響してしまうのを防ぐのに役立ちます。
uttkuttk

フックに保存しているものを変更するとどうなるか?

  • fast refreshの署名には変数の名前が含まれている事に気づいたかもしれません。
  • これは、変数を変更するとHooksの使用方法が変更されたことを意味するからです。
動画より引用
- // signature: "useState{isLoggedIn}"
+ // signature: "useState{isLoggedOut}"
function ExampleCmoponent() {
-  const [isLoggedIn, setIsLoggedIn] = useState(false);
+  const [isLoggedOut, setIsLoggedOut] = useState(false);

  // ...
}