フロントエンド開発といえば。
react アプリの初期化( npm init vite@latest <アプリ名> )

React hooksの概要 / useState / useEffect / useContext / useReducer / useMemo / useCallback / useRef

● 1. React hooks / useState()

useState() とは

・「状態を持つ変数」と「更新する関数」を管理するReactフックです。
・「状態を持つ変数」の値が変わると useState を宣言したコンポーネントで再レンダリングが起こります。
・(jsxの中でその変数が使われていてもいなくても再レンダリングがおこります)

useStateの使い方

import { useState } from "react";
const [email, setEmail] = useState<string>("");
// const [変数, 変数を更新するための関数(setter アクセサ)] = useState(状態の初期値);
// (例)変数 email / 更新する関数 setEmail() を宣言する
        <input
          type="email"
          value={email}
          onChange={e => setEmail(e.target.value)}
          placeholder="Email..."
        />

オブジェクトや配列に対して、useStateをどう使うか

const [member, setMember] = useState({ name: "", part: "" });

引用 : https://bit.ly/3nG3WHa

useStateで型指定

const [isLogined, setIsLogined] = useState<boolean>(true);

TypeScriptで中身がオブジェクトであるuseStateの初期値にnullが使えない

TypeScriptで

const [startDate, setStartDate] = useState(new Date());

の初期値を null にしたい場合は、以下のように記述します

  const [startDate, setStartDate] = useState <Date|null>(null);

useStateで更新したstateは即座に更新されるわけではない https://tyotto-good.com/blog/usestate-pitfalls

前の値を保持する時にuseRefを使います。 https://qiita.com/cheez921/items/9a5659f4b94662b4fd1e

非同期 async で使いたい時は useAsyncEffect() 関数を用意します https://github.com/jeswr/useAsyncEffect/blob/main/lib/index.ts

React.useState()の初期値は一度しかセットされない https://zenn.dev/lilac/articles/9e025186343058


● 2. React hooks / useEffect()

useEffect() とは

useEffectとは、関数コンポーネントで副作用を実行するためのhookです。
useEffectで関数コンポーネントにライフサイクルを持たせることができます
useEffectを使うと、useEffectに渡された関数はレンダーの結果が画面に反映された後(マウント時)に1回だけ動作します。( = componentDidMount() )
またクリーンアップ処理を返すとアンマウント時にも実行できます。( =componentWillUnmount()  )

useEffect の宣言方法

// 第1引数に「実行させたい副作用関数」を記述
// 第2引数に「副作用関数の実行タイミングを制御する依存データ」を記述
useEffect(() => {
  // 実行したい処理
  return () => {
    // クリーンアップの処理
  }
}, [input])

引用 : https://bit.ly/3SVp3ne
https://bit.ly/3Mn4Kwq

第2引数が指定されている場合は、マウント時以降は第2引数に渡された値が更新された時 に実行されます

初回レンダリング完了時1回だけ実行する

useEffect(() => {
    console.log('useEffectが実行されました');
},[]);
// 第2引数の配列を空にして記述すると初回レンダリング完了時(マウント時)のみ1回だけ実行されます。(実行を1回だけに限定できます)

第2引数を省略するとコンポーネントがレンダリングされるたび毎回実行されます。 (非常に危険です)

初回レンダリング完了時と任意の変数が変化したとき実行する

  const [count, setCount] = useState(0);
  useEffect(() => {
    alert('変数 count が変更されましたよ');
  }, [count]);  // 第二引数の配列に任意の変数を指定

マウント解除時に実行するにはクリーンアップ関数を返せばokです

const FunctionalComponent = () => {
 React.useEffect(() => {

   // クリーンアップ関数
   return () => {
     console.log("Bye");
   };

 }, []);
 return <h1>Bye, World</h1>;
};

初回は実行されないuseEffectのカスタムフックを作る(React)

https://zenn.dev/catnose99/scraps/30c623ba72d6b5

「useEffectを使用しないでコンポーネント直下に処理を記述」と「useEffectの第2引数を指定しない」の違いは?

以下のようなタイミングの違いがあります

・「useEffectを使用しないでコンポーネント直下に処理を記述」する場合

NoUseEffectComponent.tsx

import React, { useState } from "react"
const NoUseEffectComponent: React.FC = () => {
  console.log("=====(NoUseEffectComponent.tsx) function top =====")
  const [count, setCount] = useState(0)
  console.log("=====(NoUseEffectComponent.tsx) called =====")
  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        ({count})NoUseEffectComponent
      </button>
      {console.log("=====(NoUseEffectComponent.tsx) render last =====")}
    </>
  )
}
export default NoUseEffectComponent

・「useEffectの第2引数を指定しない」

UseEffectComponentNoSecondArgument.tsx

import React, { useEffect, useState } from "react"
const UseEffectComponentNoSecondArgument: React.FC = () => {
  console.log("=====(UseEffectComponentNoSecondArgument.tsx) function top =====")
  const [count, setCount] = useState(0)
  useEffect(() => {
    console.log("=====(UseEffectComponentNoSecondArgument.tsx) called =====")
  })
  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        ({count})UseEffectComponentNoSecondArgument
      </button>
      {console.log("=====(UseEffectComponentNoSecondArgument.tsx) render last =====")}
    </>
  )
}
export default UseEffectComponentNoSecondArgument

それぞれ実行すると次のようなタイミングとなります

useEffectを使用していないので render より前に実行される

=====(NoUseEffectComponent.tsx) function top =====
=====(NoUseEffectComponent.tsx) called =====
=====(NoUseEffectComponent.tsx) render last =====

useEffectを使用しているので render の後に実行される

=====(UseEffectComponentNoSecondArgument.tsx) function top =====
=====(UseEffectComponentNoSecondArgument.tsx) render last =====
=====(UseEffectComponentNoSecondArgument.tsx) called =====

useLayoutEffect → useEffect タイミングについて

useLayoutEffect

すべてのDOM変異後、ペイントフェーズの前に同期的に発火します。これを使用して、DOMからレイアウト(スタイルまたはレイアウト情報)を読み取り、レイアウトに基づいてカスタムDOM変異のブロックを実行します。

useEffect

レンダリングが画面にコミットされた後、つまりレイアウトとペイントフェーズの後に実行されます。視覚的な更新のブロックを避けるために、可能な限りこれを使用してください。

https://bit.ly/46jGGDZ


● 3. React hooks / useContext()

useContext() とは

Contextは、propsのバケツリレーを回避するために使用します。
グローバルなステートを管理するのに使用する Redux, Recoil とどちらを使うかについては設計段階で検討しましょう。
ReduxはMiddlewareを間に挟むことができるので、Middlewareを使いたい場合はReduxを使用します
Reduxライクで人気な zustand もおすすめです

以下の4つの要素から作成されます

・React.createContext関数でステートを作成する
・<Context.Provider> を用いて値を渡す
・<Context.Consumer> を用いて値を受け取る(re-renderの範囲を限定しやすい)
・React.useContext を用いると、Contect.Consumer と同じ役割をさせることができます(コンポーネント全体で再レンダリングが起きるのでre-render範囲を限定したい場合は、さらに子コンポーネントとして切り出す事。)

React hooksの概要 useContext による Provider|プログラムメモ


● 4. React hooks / useReducer()

useReducer() とは

引用 : https://bit.ly/3uzQaJb

useReducer で setState 関連のロジックを閉じ込める
deleteItem メソッドは、配列のうち該当する index の item を削除するメソッドであるが、こういったロジックをどこに書くかをかなり悩んできた。結論としては useReducer 内にロジックを保持するパターンが、一番疎結合である。

引用: https://bit.ly/3BeyRQw

useReducerというAPIも登場しています。 useReducerはReduxにおけるReducerのような振る舞いをします。 

引用: https://bit.ly/2Yb49ZK

useReducer が生きるのは、複数の関連したステート、つまりオブジェクトをステートとして扱うときです。

useReducerの使い方

import { useReducer } from "react";
const [state, dispatch] = useReducer(reducer, initialState);
[ステート, 発火関数 ] = useReducer(関数[, 初期値])

なお reducer は自作する必要があります。

const reducer = (state, action) => {
    
    if(action === 'INCREMENT'){
        return {count: state.count + 1}
    }else{
        return {count: state.count - 1}
    }
}

引用 : https://bit.ly/357icDb

Reduxと全く同じ使い方ですので、Reduxをさらっておくことをおすすめします。

useReducer は useState と似ている。
useState では「数値」、「文字列」、「論理値」を扱うことができるがそれを拡張して
useReducerでは「配列」、「オブジェクト」を扱えるようにする


● 5 , 6. React hooks / useMemo / useCallback

memo , useMemo , useCallback は コンポーネントのレンダリング最適化を考えるときに登場します

重たいコンポーネントの計測方法はこちらを参考にすると良いでしょう ↓ 。

【React】重い処理のあるコンポーネントはパフォーマンスチューニングまで忘れずに

・React.Memo , useMemo , useCallback の違い

以下のようにメモ化する対象が変わります。

memo → コンポーネント全体をメモ化する。
useCallback → 関数をメモ化する。(子コンポーネントに渡す関数など。)
useMemo → 変数をメモ化する。

「メモ化」とは

コンポーネントの出力を「記憶」し、同じ入力が与えられた場合に再計算を省略するものです。キャッシュですね。

参考 : https://bit.ly/40RnZp5

Reactで再レンダリングが起きる条件

・stateが更新されたコンポーネントは再レンダリング
・propsが更新されたコンポーネントは再レンダリング
・再レンダリングされたコンポーネント配下の子コンポーネントは再レンダリングされる

引用 : https://bit.ly/34YCuyH

 親コンポーネントのレンダリングによる再レンダリングを制御する方法には以下の2つがあります。

コンポーネントをメモ化する
コンポジション(props.children)を使って子コンポーネントを渡す

memo() とは

使い方

MyChild.jsx

const MyChild = () => {
  console.log('🤙 MyChildのレンダリングが開始されました');
  return (
    <div>MyChild</div>
  );
}
export default MyChild;

 ↓ このように変更することでメモ化されます

MyChild.jsx

import { memo } from "react";
const MyChild = memo( () => {
  console.log('🤙 MyChildのレンダリングが開始されました');
  return (
    <div>MyChild</div>
  );
})
export default MyChild;

全体を memo() で囲っておきます。このようにすることで親コンポーネントに変更があった場合に MyChild は再レンダリングはされなくなります。

useMemo() とは

useMemoとは変数に対して memo化を行うものです。
useMemoは、以前行われた計算の結果をキャッシュし、useMemoが属するコンポーネントが再レンダリングしても、useMemoの第2引数(deps)が更新されていない場合は計算をスキップして以前の結果を使うという機能を提供します。

引用 : https://bit.ly/3tpOgw4

useCallback() とは

再レンダリングを抑えるための手法
useCallbackはパフォーマンス向上のためのフックで、メモ化したコールバック関数を返します。
useEffectと同じように、依存配列(=[deps] コールバック関数が依存している要素が格納された配列)の要素のいずれかが変化した場合のみ、メモ化した値を再計算します。

引用 https://blog.uhy.ooo/entry/2021-02-23/usecallback-custom-hooks/

const App: React.VFC = () => {
  const handleClick = useCallback((e: React.MouseEvent) => {
    console.log("clicked!");
  }, []);

  return (
    // memo化されたコンポーネント
    <SuperHeavyButton onClick={handleClick} />
  );

こちらのように、SuperHeavyButtonをmemo化 + props を useCallback する事で、App コンポーネントが再レンダリングされた際にもSuperHeavyButtonはすでに生成されたものが再利用されます

・値が MyData | undefined のように 初期値 undefined な値を使って新たに変数を生成する時に使用する。

例えば ReactQuery の カスタムフックで以下のように使用してる場合に

const { data } = useMySampleQuery() // 型は  MyData | undefined
const dataWithGetter = withGetter( data ) // undefined を受け付けない場合

 ↓ useMemo を使って次のようにすることができます

const { data } = useMySampleQuery() // 型は  MyData | undefined
const dataWithGetter = withGetter( data ) // undefined を受け付けない場合

const dataWithGetter = useMemo(() => {
  return data ? withGetter( data ) : undefined;
}, [data]);


● 7. React hooks / useRef

useRef() とは

2つの使い方があります。

1. DOMにアクセスするための ref として使用する

関数コンポーネントでは、Classコンポーネント時のref属性の代わりに、useRefを使って要素への参照を行います。
const Child = React.forwardRef((props, ref) => {
    return (
        <div ref={ref}>DOM</div>
    );
});

const Component = () => {
    const el = useRef(null);

    useEffect(() => {
        console.log(el.current);
    }, []);

    return (
        <Child ref={el} />
    );
};

2. 再レンダリングを発生させないステートとして使用する

useStateを利用している場合はstateの変更される度にコンポーネントの再レンダリングが発生しますが、
useRefは値が変更になっても、コンポーネントの再レンダリングは発生しません。
コンポーネントの再レンダリングはしたくないけど、内部に保持している値だけを更新したい場合は、
保持したい値をuseStateではなく、useRefを利用するのが良いです。
useState と比較したとき useRef の重要な特徴は3つです。

更新による再レンダリングが発生しない
値が同期的に更新される
返却されるRefオブジェクトは同一である

引用 : https://bit.ly/3C9oMGv

こちらでは preact の useSignal との比較が記述されています
https://www.builder.io/blog/usesignal-is-the-future-of-web-frameworks

引用: https://bit.ly/3zXdqC2

refを「ref」propとして渡さないでください。これはReactで予約されている属性名であり、エラーが発生します。
代わりに、forwardRefを使用します

● preact の Signalsを react で使いたい場合はこちらを参考に

https://github.com/preactjs/signals

● その他 hooks について読んでおくと良い記事

https://zenn.dev/kobayang/articles/9145de86b20ba6

React.memo と useCallbackで state の変化に伴う{個別,共通}コンポーネントの再描画を抑制する - DEV Community

No.2055
03/01 11:30

edit