TL;DR
このページでは、Next.js と TypeScript を使って、"React フックが少し分かる!React フックチートシート"の実装方法について解説しますね。一言でいうと、React フックとは状態管理やライフサイクルを関数コンポーネントから制御するための仕組みです。
開発環境 | バージョン |
---|---|
Next.js | 13.4.4 |
TypeScript | 5.0.4 |
Emotion | 11.11.0 |
React | 18.2.0 |
React フックとは?
React フックとは、React の関数コンポーネントで状態やライフサイクルを制御するための仕組みです。
ライフサイクルとは?
React コンポーネントの「ライフサイクル」は、人間が生まれてから成長し、そして歳をとる過程に少し似ています。ライフサイクルは、コンポーネントが生まれる(マウント)から消える(アンマウント)までの過程を指します。それでは、その詳細を見てみましょう。
-
誕生(マウント): コンポーネントが誕生するとき、まず必要なデータを準備します。例えば、親コンポーネントから受け取る情報(props)を用意したり、初期状態を設定したりします。そして、最初の描画が行われ、コンポーネントが画面(DOM)に現れます。
-
成長(更新): コンポーネントが一度表示された後も、何かしらの変更(例えば、ユーザーのクリック操作や新たな情報の受け取りなど)があると、コンポーネントは成長(更新)します。このとき、新しい状態や props に基づいて再描画が行われます。
-
老い(アンマウント): コンポーネントが必要なくなったとき、あるいはページが変わったときなど、コンポーネントは「老い」を迎えます。この状態をアンマウントといい、ここでコンポーネントは画面から消えます。これまで使用していたリソース(例えば、タイマーやネットワークリクエストなど)をきちんと片付けることが重要です。
これらすべてのステージが、React コンポーネントの「ライフサイクル」です。それぞれのステージで適切な処理を行うことで、ユーザーにとって快適な体験を提供することができます。
クラスコンポーネントでしか利用できなかった多くの機能を関数コンポーネントでも使用できるようにするものです。これにより、コンポーネントの再利用やコードの理解、読みやすさを向上させることができます。
import React, { useState } from 'react'
// Props の型定義
type Props = {
initialCount: number
}
const Counter = ({ initialCount }: Props) => {
const [count, setCount] = useState(initialCount)
return (
<div>
Count: {count}
<button onClick={() => setCount(count + 1)}>Increase</button>
<button onClick={() => setCount(count - 1)}>Decrease</button>
</div>
)
}
このサンプルコードは、useState
フックを使ってカウンターの状態を管理しています。useState
フックは、初期値を引数に取り、状態の値とその状態を更新するための関数のペアを返します。
useState
useState は関数コンポーネント内で状態を維持・管理するためのフック(機能) です。
状態の現在の値と、その値を更新するための関数をペアにして返します。具体的には、値を保持し、それを更新することが可能です。また、その値が変更されるとコンポーネントが再レンダリングされ、新しい値が画面に反映されます。
useState
というのは、ある値(例えば数字や文字列、リストなど)を保存しておき、その値を変えたり見たりするための機能です。これは、例えばゲームをプレイしているときに、得点を記録しておくために使われます。
イメージとしては、useState
は魔法の箱のようなものです。この箱に何かを入れておくと、後でその中身を取り出したり、新しいものに入れ替えたりできます。そして、その箱の中身が変わるたびに、React はそれを検知して画面を更新します。
例えば、あるボタンをクリックしたときに、得点を 1 増やすというケースを考えてみましょう。
import React, { useState } from 'react'
const ScoreCounter = () => {
const [score, setScore] = useState(0)
const incrementScore = () => {
setScore(score + 1)
}
return (
<div>
<p>得点: {score}</p>
<button onClick={incrementScore}>得点を増やす</button>
</div>
)
}
このコードでは、useState
を使ってscore
という名前の箱を作り、初めは 0 を入れています。そして、「得点を増やす」ボタンをクリックするたびに、setScore
を使って得点を 1 増やしています。これにより、ボタンを押す度に得点が増え、画面上の得点もそれに合わせて更新されます。
useState
は、このように React で状態を管理するための重要なツールであり、関数コンポーネントの中で使われます。
useEffect
useEffect はコンポーネントが描画された後に特定の処理を行うためのフックです。
副作用(データのフェッチ、手動での DOM の更新、など)を実行するためのものです。
例えば、API からデータを取得する、タイマーを設定する、DOM の属性を直接操作するなど、コンポーネントが画面に表示された後に行いたい処理があるときに使います。
useEffect は、特定の処理をコンポーネントが描画された後に自動的に行います。例えば、ユーザーの操作を待つことなく API からデータを取得したり、タイマーを設定したりできます。これにより、アプリケーションはユーザーとスムーズに対話することが可能になります。
関数コンポーネントの中で直接副作用を起こすと、レンダリングのたびにそれらが実行されてしまう可能性があります。それを防ぐために、useEffect フックを使用します。
import React, { useState, useEffect } from 'react'
const Clock = () => {
const [date, setDate] = useState(new Date())
useEffect(() => {
const timerID = setInterval(() => {
setDate(new Date())
}, 1000)
return () => {
clearInterval(timerID)
}
}, [])
return <div>{date.toLocaleTimeString()}</div>
}
このコードでは、useEffect フックを使って 1 秒ごとに現在の時刻を更新するようにしています。また、クリーンアップ関数として clearInterval を返しています。
useContext
useContext フックは、React の Context API を使いやすくするためのものです。
コンテキストを通じて、コンポーネントツリー全体にデータを提供することができます。このフックは、コンテキストオブジェクト(React.createContext で作成)を引数に取り、そのコンテキストの現在の値を返します。
import React, { useContext } from 'react'
const ThemeContext = React.createContext('light')
const ThemedButton = () => {
const theme = useContext(ThemeContext)
return <button>{theme === 'light' ? 'Light Theme' : 'Dark Theme'}</button>
}
このコードでは、useContext フックを使ってテーマコンテキストの値を取得しています。この値は、ボタンのラベルに使用されています。
useRef
useRef は不変の参照オブジェクトを生成するためのフックです。
DOM 要素への参照(例えば、特定の input 要素)を保持したり、レンダリング間で値を維持する必要があるときに使用します。これは、時間経過やレンダリングにより変わらない値を持つことができるため、タイマーやインターバルの ID を保持する際などにも有用です。
useRef
は、DOM 要素や時間をまたいで保持するべき値の参照を作成します。この参照は更新してもコンポーネントの再レンダリングを引き起こしません。例えば、特定の入力要素にアクセスしたいときや、特定のタイマーの ID を保存しておきたいときなどに使います。
useRef
フックは、ミュータブルな ref オブジェクトを生成するためのものです。このオブジェクトのプロパティ.current は、初期化時に引数で与えられた値(初期値)を持ち、その値は変更できますが、その変更は再レンダリングを引き起こさない点が特徴です。
import React, { useRef } from 'react'
const TextInput = () => {
const inputRef = useRef<HTMLInputElement>(null)
const handleClick = () => {
inputRef.current?.focus()
}
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Focus the input</button>
</div>
)
}
このコードでは、useRef フックを使って input 要素への参照を作成しています。そして、ボタンがクリックされたときに、その input 要素にフォーカスを当てるようにしています。
useMemo
useMemo フックは、複雑な関数の結果をメモ化(一度計算した結果を再利用)するためのものです。
関数とその依存配列を引数に取り、関数の返り値をメモ化します。依存配列の要素が変更されると、関数は再実行されます。
import React, { useMemo } from 'react'
const calculateExpensiveValue = (a: number, b: number) => {
// This is just an example of an expensive computation.
let result = 0
for (let i = 0; i < 1000000000; i++) {
result = a * b + i
}
return result
}
const Component = ({ a, b }: { a: number; b: number }) => {
const value = useMemo(() => calculateExpensiveValue(a, b), [a, b])
return <div>Value: {value}</div>
}
このコードでは、useMemo フックを使って重い計算の結果をメモ化しています。この結果は、依存配列の値(ここでは a と b)が変更されるまで再計算されません。
useCallback
useCallback フックは、useMemo フックと同様に、メモ化を利用しますが、この場合は関数そのものをメモ化します。
これは、特に関数を props として下位コンポーネントに渡すときに便利です。
import React, { useState, useCallback } from 'react'
const Button = ({ onClick }: { onClick: () => void }) => <button onClick={onClick}>Increase</button>
const Counter = () => {
const [count, setCount] = useState(0)
const increase = useCallback(() => {
setCount(count + 1)
}, [count])
return (
<div>
Count: {count}
<Button onClick={increase} />
</div>
)
}
このコードでは、useCallback フックを使って関数をメモ化しています。この関数は、Button コンポーネントに props として渡されます。
useReducer
useReducer は、より複雑なコンポーネント状態ロジックを管理するためのフックです。
useState よりも詳細な状態遷移を含む場合や、同じ状態遷移ロジックを多くのコンポーネント間で共有する場合に有効です。
import React, { useReducer } from 'react'
type State = { count: number }
type Action = { type: 'increment' | 'decrement' }
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
default:
return state
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
)
}
この例では、useReducer フックを使ってカウンターコンポーネントを作成しています。reducer 関数は現在の状態とアクションを引数に取り、新しい状態を返します。
useLayoutEffect
useLayoutEffect
は、useEffect
と非常に似ていますが、レンダリングのフェーズが異なります。useLayoutEffect
内の関数は、ブラウザが画面を描画する前に実行されます。これは、DOM の変更を即時に反映させたい場合や、レンダリングをブロックして副作用を先に行いたい場合に有効です。
import React, { useLayoutEffect, useState } from 'react'
const Component = () => {
const [width, setWidth] = useState(window.innerWidth)
useLayoutEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return <div>Window width: {width}</div>
}
この例では、useLayoutEffect
を使ってウィンドウの幅を監視し、リサイズイベントが発生するたびにウィンドウの幅を更新しています。
useDebugValue
useDebugValue フックは、React 開発者ツールでカスタムフックをより便利にデバッグするためのフックです。
このフックを使用すると、開発者ツールにカスタムフックからのデバッグ情報を表示することができます。
import React, { useState, useDebugValue } from 'react'
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null)
// Show friend status in DevTools.
useDebugValue(isOnline ? 'Online' : 'Offline')
// ...
return isOnline
}
この例では、useDebugValue
を使って開発者ツールにフレンドのオンラインステータスを表示しています。
カスタムフック
カスタムフックは、コンポーネントのロジックを共有するためのメカニズムです。独自のフックを作成することで、状態ロジックを再利用しやすくすることができます。
カスタムフックは、名前が "use" で始まる JavaScript の関数です。この関数内では、他のフック(useState、useEffect など)を呼び出すことができます。
import React, { useState, useEffect } from 'react'
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth)
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return width
}
const Component = () => {
const width = useWindowWidth()
return <div>Window width: {width}</div>
}
この例では、ウィンドウの幅を管理するロジックをカスタムフックuseWindowWidth
として作成しています。このカスタムフックは、他のコンポーネントで再利用可能です。