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

● 1. React hooks / useState()

useState() とは

「状態を持つ変数」と「更新する関数」を管理するReactフックです。
「状態を持つ変数」の値が変わると useState を宣言したコンポーネントで再レンダリングが起こります。

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 =====


● 3. React hooks / useContext()

useContext() とは

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

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

・React.createContext関数でステートを作成する
・<Context.Provider> を用いて値を渡す
・<Context.Consumer> を用いて値を受け取る(再レンダリングが起きないので、パフォーマンスを気にする場合はこちら)
・React.useContext を用いると、Contect.Consumer と同じ役割をさせることができます(再レンダリングが起きやすい)

1. ・React.createContext関数でステートを作成する

値を更新する関数も createContext で同じオブジェクトに入れています。(分けても良い)

type MyContextValue = {
  myvalue: number                    // 値
  setMyvalue: VoidFunction    // 値を更新する関数
}

export const MyContext = createContext<MyContextValue>({} as MyContextValue) // デフォルト値を {} とする。

2. ・<Context.Provider> を用いて値を渡す

  const [myvalue, setMyvalue] = useState<number>(0)

  const setMyvalueFunction = () => {
    setMyvalue(myvalue + 1)
  }

  const values = {
    myvalue: myvalue,
    setMyvalue: setMyvalueFunction
  }

return(
<MyContext.Provider value={values}>
   ...(ここに値を受け取りたいコンポーネントを記述する)
</MyContext.Provider>
)

3.・<Context.Consumer> を用いて値を受け取る

( ↓ の React.useContext を使う方がソースが見やすくなりますが、再レンダリングが起きないのが特徴です。)

const SampleSub1 = () => {
  return (
    <>
      <MyContext.Consumer>
        {value => {
          return (
            <>
              <h1>Hello SampleSub1!</h1>
              <h3>{value.myvalue}</h3>
            </>
          )
        }}
      </MyContext.Consumer>
    </>
  )
}

3.・React.useContext を用いて値を受け取る

こちらの方が記述がシンプルになるのでおすすめです。

const SampleSub2 = () => {
  const { myvalue } = useContext(MyContext)

  return (
    <>
      <h1>Hello SampleSub2!</h1>
      <h3>{myvalue}</h3>
    </>
  )
}


● 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 / useCallback / useMemo

React.memo / React.useMemo / React.useCallback は コンポーネントのレンダリング最適化や無限ループ防止を考えるときに登場します

useCallbackは関数そのものをメモ化します。
useMemoは関数の戻り値をメモ化します。

おもにファンクションの場合にuseCallbackを、
オブジェクトの場合にuseMemoを使用します。

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() とは

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


● 7. React hooks / useRef

useRef() とは

関数コンポーネントでは、Classコンポーネント時のref属性の代わりに、useRefを使って要素への参照を行います。

useStateを利用している場合はstateの変更される度にコンポーネントの再レンダリングが発生しますが、
useRefは値が変更になっても、コンポーネントの再レンダリングは発生しません。

コンポーネントの再レンダリングはしたくないけど、内部に保持している値だけを更新したい場合は、保持したい値をuseStateではなく、useRefを利用するのが良さそうです。

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

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

edit