【React/Next.js】基本的なReact Hooksについてまとめた

先月から仕事でReactとNext.jsを使い始めたので、今のところ実務で使った基本的なReact Hooksについてまとめます。

(今後この記事に書いてないフックを使うことになったら良いタイミングで更新していこうと思います)

目次

React Hooksとは

公式ドキュメントでは以下のとおり説明されています。

フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。

https://ja.reactjs.org/docs/hooks-reference.html

僕は「関数コンポーネントで使える便利機能」だと捉えます。

フックの登場で変わったことを説明するために箇条書きでまとめます。

  • 前提としてReactでコンポーネントを作成する方法はクラスコンポーネントと関数コンポーネントがある
  • クラスコンポーネントには状態管理ライフサイクルの機能があるが、React Hooks登場前までは関数コンポーネントではそれらの機能がなかった。
  • React Hooksは関数コンポーネントで使える便利機能(フックはクラスの内部では動作しない)
  • フックを使えば状態管理やライフサイクルを扱うことができる
  • フックを使用すればクラスコンポーネントでできていたことが、関数コンポーネントでもできる(関数コンポーネントの方が書きやすい)

僕自身はReact Hooksが登場して、コンポーネントの書き方として関数コンポーネントが主流になってからReactに入門したのでクラスコンポーネントのことはわかりませんが、React Hooksの登場が最近のReactがアツいと言われている大きな要因だと認識しています。

基本的なReact Hooks

というわけで個人的に実務で使った基本的なフックをピックアップしてまとめておきます。

useState

useStateはReact Hooksの中でも1番使用頻度が高いフックで、関数コンポーネントでstateを管理するためのフックです。

「stateの管理」とはstate(=コンポーネント内部で保持する「状態」)の「保持」と「更新」を指します。

useStateの基本形はこちら。

const [state, セット関数] = useState(stateの初期値)

実際に使うとするとこのような形で使います。

// nameというstateとnameを更新するsetNameを定義。初期値は空文字
const [name, setName] = useState('')

TypeScriptを使う場合、初期値を''に設定した時点でnameがstring型と型推論してくれるので明示的に型定義する必要はない(この1文ならわざわざ明示的に型定義しなくてもstring型であることはわかる)ですが、もし型定義するならこのように書きます。

// 型定義する場合
const [name, setName] = useState<string>('')

useState(state自体を含む)の特徴をまとめます。

  • stateはコンポーネント内部でしか使えない
  • ページをリロードするとstateはリセットされる(初期値になる)
  • stateの値はセット関数を使って更新する
  • stateが更新されるとコンポーネントは再レンダリングされる
  • 再レンダリング後もstateの値は保持され、最新の state の値を関数に渡す

state管理で僕が少しハマったこと↓

useRouter

useRouterは名前の通りルーティング情報を扱うフックです。

関数コンポーネント内でルーターオブジェクトにアクセスしたいときに使用します。

(useRouterはimport元がnext/dist/client/routerであり、Next.jsでしか使えません)

useRouterは以下のフックを1つのまとめたものという認識です。

  • useParams
  • useLocation
  • useHistory
  • useRouteMatch

ルーターオブジェクトは様々なプロパティを持ち、これらに簡単にアクセスできるようになります。

  • pathname
  • query
  • asPath
  • isFallback
  • locale
  • basePath
  • locales
  • defaultLocale
  • domainLocales
  • isPreview
  • isReady

よく使うのは以下のコードのようにどこかのボタンを押した時にアプリ内の別ページに遷移するような処理ではないでしょうか。
(ページ遷移はLinkタグを使えばいいだろ、という声があるかもですが、ボタンをコンポーネントとして再利用する場合はクリックイベントをpropsとして渡す形にした方が再利用しやすいと思っています)

const router = useRouter()

// クリックイベントで/sampleに遷移する
const handleClick = () => router.push('/sample')

push以外もrouter.の形で実行できるメソッドがありますが、現状使ったことがないのでpushだけ挙げました。

また、色々調べた中で「あ、これ便利だ」と思ったものはこちらの使い方です。

// 認証が必要なページで条件によってログイン画面に遷移させる(リダイレクト的な動作)

useEffect(() => {
  if (条件) {
    router.push('/login')
  }
}, [依存配列])

この処理も僕はまだ実装したことがないですが、添付しているQiitaの記事から引用させていただきましたm(__)m

useCallback

useCallbackはメモ化されたコールバック関数を返すフックです。

使用用途はコンポーネントのレンダリング最適化によるパフォーマンス向上。

基本的な書き方は以下。(公式ドキュメントから引用)

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

第一引数にはコールバック関数を、第二引数にはコールバック関数の依存配列を渡します。

アロー関数は通常、定義しているコンポーネントが再レンダリングされた時に再生成されてしまうため、仮に子コンポーネントにpropsとしてアロー関数を渡している場合、その子コンポーネントをメモ化していても、propsが更新されたとみなされて再レンダリングされてしまいます。(以下のコード)

// importは省略
// ChildComponentはメモ化しているけど、ParentComponentが再レンダリングされた時に、
// handleClickが再生成されてCountUpButtonコンポーネントに渡されるため、
// props更新扱いになる

export const ParentComponent = () => {
  const [count, setCount] = useSate(0)
  const handleClick = () => {
    setCount(count + 1)
  }
  
  return (
    <p>現在の数字:{count}</p>
    <CountUpButton onClick={handleClick}>+1する</CountUpButton>
  )
}

type Props = {
  onClick: () => void
  children: ReactNode
}

export const CountUpButton = memo(({ onClick, children }: Props) => {
  return (
    <button onClick={onClick}>{children}</button>
  )
})

useCallbackを使うことで依存配列の中にある変数やstateが更新された時だけコールバック関数を再生成するように制御することができます。

上記のコード例だと、useCallback関数を使っても親コンポーネントの再レンダリング時に子コンポーネントも再レンダリングされてしまう(サンプルが悪かったですね…)のですが、stateが増えていくと、条件によってレンダリング回数を減らすことができます。

なお、依存配列が空配列の場合は初回レンダリング時のみに生成されるアロー関数となります。

コールバック関数って何?という方にはこちらの記事を読むのをオススメします!(とても分かりやすかったです)

useEffect

useEffectは関数の実行タイミングをコンポーネントのレンダリング後に遅らせることができるフックです。

クラスコンポーネントでの以下のライフサイクルメソッドに当たるフックで、副作用のある処理を関数コンポーネントで扱うことができます。

  • componentDidMount:コンポーネントのレンダリング完了後
  • componentDidUpdate:コンポーネントの再レンダリング完了後
  • componentWillUnmount:コンポーネントがアンマウントされて破棄された時

ちなみに副作用のある処理については公式ドキュメントでは以下のように書かれています。

DOM の書き換え、データの購読、タイマー、ロギング、あるいはその他の副作用を、関数コンポーネントの本体(React のレンダーフェーズ)で書くことはできません。それを行うと UI にまつわるややこしいバグや非整合性を引き起こします。

https://ja.reactjs.org/docs/hooks-reference.html#useeffect

基本形は以下。

// 第一引数にはuseEffectの効果を適用する関する
// 第二引数にはuseEffectの中の関数の依存データを配列で指定する。
// この中に指定された値が更新されたら関数を実行する
useEffect(関数, [])

コンポーネントがアンマウントされたタイミング(例えば別のページに遷移した時)での処理を定義する場合を含めるとこのような形になります。

useEffect(
  // 初回レンダリング後、再レンダリング後の処理
  () => {}
 // コンポーネントがアンマウントされた時の処理
  return () => {},
  []
)

また初回レンダリング、再レンダリング後には処理不要でアンマウント後のみ処理したい場合はこのように書くこともできます。

useEffect(
    () => () => {
      // 処理
    },
    [],
  )

その他のuseEffectの特徴は以下。(繰り返しの部分もあります)

  • 依存配列を定義していない場合はコンポーネントがレンダリングされる度に実行される
    • 無限ループの温床になりやすいので、依存配列を定義しないことはほぼない
  • 依存配列が空配列の場合は初期レンダリング時のみ実行される
    • 画面を表示した時にデータを取得する等の処理で結構使われる

useContext

(useContextはまだ実務で使ったことがないのですが、調べた&もうすぐ使うということでまとめておきます。)

useContextはその言葉の通りですが、Contextの機能を簡単に扱えるようにしたフックです。

Contextとは何かというとこのような特徴があります。

  • Reactにデフォルト搭載されたAPI
  • グローバルなstateを管理できる
  • stateなどのデータをpropsのバケツリレーで深い階層のコンポーネントに渡すことなく、データアクセスができる
  • コンポーネントの再利用を難しくするため、慎重に使う必要がある(まだ体感していません)

Contextの基本的な使い方。

// Context定義側
export const SampleContext = createContext({})

export const SampleContextProvider = ({ children }) => {
  const { children } = props;
  const sampleState = 'Contextに渡すstateです'

  return (
    // ContextにsampleStateというグローバルに管理するstateを渡す
    <SampleContext.Provider value={{ sampleState }}>
      {children}
    </SampleContext.Provider>
  );
};

// Contextを設定する側
export const SampleComponent = () => {
  return (
     // SampleContextProviderタグで囲ったコンポーネント内ではContextを参照できるようになる
     <SampleContextProvider>
       <ChildComponent />
     </SampleContextProvider>
  )
}

// Contextにアクセスする側(ここでuseStateを使う)
export const ChildComponent = () => {
  // useContextの引数に定義済みのContextを指定する
  const context = useContext(SampleContext)
  console.log(context) // { sampleState: 'Contextに渡すstateです' }
  return (
     <p>{context.sampleState}</p>
  )
}

ちょっと使い方がややこしいですね。。

useContextを使うためにはcreateContext、Providerを使う必要があります。

Contextに渡したデータ(上記コードでいうとsampleState)を使用しているコンポーネントはuseStateで扱うローカルなstateと同じく、値が更新されると再レンダリングされます。

なので、子コンポーネントがある場合はメモ化を忘れないようにする必要があります。

Contextを使う場合は、どのコンポーネントが再レンダリングされるのかを意識しないとレンダリングコストが高くなりパフォーマンス低下につながるので注意ですね。

最後に

React、Next.jsでの開発で必須で使うであろうフックについてまとめました。

まだまだ入門したばかりでこれから他のフックも使っていくと思うので気が向いたら更新していこうと思います。

React Hooksの学習に役立った記事や講座を最後に紹介しています。

参考記事・講座

JS、TS、React全般

React Hooks全体

useState

useRouter

useCallback

useEffect

useContext

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!

この記事を書いた人

Webエンジニア←KHI(プラント事業の技術職)←大学院(機械工学)

PHP/Laravel/TypeScript/React/Next.js(Vue.js/Nuxt.jsは少し)
バックエンド歴の方が長いです。

神戸で「つながる勉強会」を運営↓
https://tsunagaru-kobe.connpass.com/

コメント

コメント一覧 (1件)

コメントする

目次
閉じる