まえがき
React Hooks の自分用ざっくりまとめです。
誤りなどがあればご指摘頂けると幸いです。
useState
useState()
は関数コンポーネントでの状態(state)を管理するためのフック。
const [state, state更新関数] = useState(初期値)
のように定義する。
import { useState } from "react";
/**
* useState() は関数コンポーネントでの状態(state)を管理するためのフック。
* const [state, state更新関数] = useState(初期値) のように定義する。
*
* setState() は state を更新するために利用できる関数で、
* state を直接引数で渡す書き方①と
* 直前の state から新しい state を計算する関数を渡す書き方②がある。
*/
const Counter = () => {
// 初期値0で count を定義
const [count, setCount] = useState(0);
// ① 現在の count に直接 +1 して更新する
const increment = () => setCount(count + 1);
// ② 更新直前の count を受け取り、それを +1 して更新する
const incrementByPreState = () => setCount((prevCount) => prevCount + 1);
return (
<>
<h2>useState</h2>
<div>Count: {count}</div>
<button onClick={increment}>Count Up ①</button>
<button onClick={incrementByPreState}>Count Up ②</button>
</>
);
};
export default Counter;
useEffect
useEffect()
は第1引数に渡された関数(副作用関数)の実行タイミングをレンダリング後まで遅らせるフック。
第2引数に依存配列を渡すことで副作用関数の実行タイミングを制御できる。
import { useEffect, useState } from "react";
/**
* useEffect() は第1引数に渡された関数(副作用関数)の実行タイミングをレンダリング後まで遅らせるフック。
* 第2引数に依存配列を渡すことで副作用関数の実行タイミングを制御できる。
*/
const App = () => {
const [users, setUsers] = useState([]);
// 依存配列が空の useEffect() でマウント後に1度のみ副作用(関数)を実行
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((users) => setUsers(users));
}, []);
return (
<>
<h1>ユーザー一覧</h1>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
};
export default App;
useEffect + Dependency Array
useEffect に依存配列を指定すると、
指定した値に変更があった場合に副作用関数を実行する。
import { useEffect, useState } from "react";
/**
* useEffect に依存配列を指定すると、
* 指定した値に変更があった場合に副作用関数を実行する。
*/
const Counter = () => {
const [count, setCount] = useState(0);
// 依存配列に count を指定しているため、Count Upボタンを押下するたびに
// count の値が変動し、副作用関数が実行されログが出力される。
useEffect(() => {
console.log("Count Up");
}, [count]);
return (
<>
<h2>useEffect(Dependency Array)</h2>
<div>Count: {count}</div>
<button onClick={() => setCount(count + 1)}>Count Up</button>
</>
);
};
export default Counter;
useEffect + Cleanup Function
useEffect()
の副作用関数内で return
を書くことでクリーンアップ関数を作成できる。
クリーンアップ関数を return
することで2回目以降のレンダリング時に前回実行した
副作用を打ち消すことができ、タイマーのキャンセルやイベントリスナーの削除などで利用できる。
import { useEffect, useState } from "react";
/**
* useEffect の副作用関数内で return を書くことでクリーンアップ関数を作成できる。
* クリーンアップ関数を return することで2回目以降のレンダリング時に前回実行した
* 副作用を打ち消すことができ、タイマーのキャンセルやイベントリスナーの削除などで利用できる。
*/
const Timer = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
console.log("working", count);
setCount(count + 1);
}, 1000);
// 親コンポーネントで制御している Timer コンポーネントの表示・非表示時に
// clearInterval を実行してログ出力がアンマウント後に止まるようにしている。
return () => {
clearInterval(id);
};
});
return <h2>Timer Counts: {count}</h2>;
};
export default Timer;
import { useState } from "react";
import Timer from "./Timer";
const App = () => {
const [show, setShow] = useState(true);
return (
<>
{/* Timer コンポーネントは表示中にログを出力し続ける機能を有している。 */}
{show && <Timer />}
<button onClick={() => setShow(!show)}>Toggle</button>
</>
);
};
export default App;
useEffect + Cleanup + EventListener
addEventListerner()
でイベントをバインドした場合、マウントのたびにイベントが多重にバインドされてしまわないようにするため、 removeEventListerner()
をクリーンアップ時に実行する。
import { useEffect, useState } from "react";
const App = () => {
const [windowWidth, setWindowWidth] = useState(0);
useEffect(() => {
function handleResize() {
setWindowWidth(window.innerWidth);
}
// 画面描画時に画面幅情報を取得
handleResize();
// 画面リサイズ時に画面幅情報を再取得
window.addEventListener("resize", handleResize);
// クリーンアップで resize イベントへのバインドを解除し、多重にバインドされないようにする。
return () => window.removeEventListener("resize", handleResize);
}, []);
return (
<>
<h1>useEffect + Cleanup (EventListener)</h1>
<p>windowWidth: {windowWidth}</p>
</>
);
};
export default App;
useEffect + async/await
useEffect()
の第1引数の副作用関数は戻り値無し or クリーンアップ関数を設定する必要があり、
非同期関数を設定すると戻り値が Promise型 となりエラーになるため、
副作用関数内で非同期関数を定義して利用するか、非同期の即時関数を使って実装する。
import { useEffect, useState } from "react";
/**
* useEffect の第1引数の副作用関数は戻り値無し or クリーンアップ関数を設定する必要があり、
* 非同期関数を設定すると戻り値が Promise型 となるためエラーになるため
* 副作用関数内で非同期関数を定義して利用するか、非同期の即時関数を使って実装する。
*/
const App = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
// 非同期の関数を定義するパターン
const fetchUsers = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await res.json();
setUsers(users);
};
fetchUsers();
// 非同期の即時関数で実装するパターン
// (async() => {
// const res = await fetch("https://jsonplaceholder.typicode.com/users");
// const users = await res.json();
// setUsers(users);
// })()
}, []);
return (
<>
<h2>useEffect(async/await)</h2>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
};
export default App;
useRef
useRef()
は画面の要素への参照を実行し、再レンダリングを行わずに保持している値を更新することができる。(stateの更新時は再レンダリングされる)
import { useRef } from "react";
// useRef は画面の再レンダリングを行わずに保持している値を更新することができる。(stateの更新時は再レンダリングされる)
const App = () => {
// useRef は .current プロパティが渡された引数を inputRef へ返す。
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.value = "useRef"; // input の値を書き換え
inputRef.current.focus(); // input にフォーカス
console.log(inputRef.current);
};
return (
<>
<h1>useRef</h1>
<div>
{/* ref={inputRef} でDOMの参照をする */}
<input type="text" ref={inputRef} />
</div>
<button onClick={handleClick}>Focus Input</button>
</>
);
};
export default App;
useRef + useState
import { useRef, useState } from "react";
const App = () => {
const inputRef = useRef("");
const [text, setText] = useState("");
// useRef で参照している input の値更新時は再レンダリングされないため、
// useState での state 更新時のみ再レンダリングされログが出力される。
console.log("Rendering!");
return (
<>
<h1>useRef + useState</h1>
<div>
<input ref={inputRef} type="text" />
</div>
<button onClick={() => setText(inputRef.current.value)}>
Set Text by useState
</button>
<p>Text: {text}</p>
</>
);
};
export default App;
useRef + form
useRef()
でフォーム要素を参照しデータを保持/更新することで、 useState()
を使わなくても再レンダリングを行わずにフォームを実装することができる。
import { useRef } from "react";
const App = () => {
// useRef で DOM への参照を生成
const emailRef = useRef(null);
const passwordRef = useRef(null);
const handleSUbmit = (e) => {
e.preventDefault();
console.log(
`email: ${emailRef.current.value}, password: ${passwordRef.current.value}`
);
};
return (
<div>
<h1>Form with useRef</h1>
<form onSubmit={handleSUbmit}>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" ref={emailRef} />
</div>
<div>
<label htmlFor="password">Password</label>
<input id="password" type="password" ref={passwordRef} />
</div>
<div>
<button type="submit">LOGIN</button>
</div>
</form>
</div>
);
};
export default App;
useReducer
useReducer()
は状態管理のためのフックで、 useState()
を内部実装している。
(state, action) => newState
という型の reducer
を受け取り、
現在の state
とそれを更新するための dispatch
関数を返す。
import { useReducer } from "react";
// 初期値を定義
const initialState = {
count: 0,
actionCount: 0
};
// state を更新するための関数で、 dispatch で呼び出して利用する。
// reducer では複数の state を同時に更新することもできる。
const reducer = (state, action) => {
switch (action) {
case "INCREMENT":
return {
count: state.count + 1,
actionCount: state.actionCount + 1
};
case "DECREMENT":
return {
count: state.count - 1,
actionCount: state.actionCount + 1
};
case "RESET":
return {
count: 0,
actionCount: 0
};
default:
return state;
}
};
const App = () => {
/**
* useReducer は状態管理のためのフックで、 useState を内部実装している。
* (state, action) => newState という型の reducer を受け取り、
* 現在の state とそれを更新するための dispatch 関数を返す。
*/
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<h1>useReducer</h1>
<p>Count: {state.count}</p>
{/* dispatch('actionType') で action を指定し実行する */}
<button onClick={() => dispatch("INCREMENT")}>INCREMENT</button>
<button onClick={() => dispatch("DECREMENT")}>DECREMENT</button>
<p>Action Count: {state.actionCount}</p>
<button onClick={() => dispatch("RESET")}>RESET</button>
</>
);
};
export default App;
useContext
useContext()
を利用することで props を子コンポーネントにバケツリレーする必要がなくなる。
Provider - データを渡す側: createContext()
で生成し Consumer をラップする
Consumer - データを受け取る側: useContext(contextName)
でデータを利用できる
import { createContext } from "react";
import Content from "./Content";
/**
* useContext を利用することで props を子コンポーネントにバケツリレーする必要がなくなる。
*
* Provider: データを渡す側 createContext() で生成し Consumer をラップする
* Consumer: データを受け取る側 useContext(contextName) でデータを利用できる
*/
// createContext で Context オブジェクトを生成する。(初期値を入れることも可能)
export const UserContext = createContext();
const App = () => {
// Consumer(子コンポーネント) で利用する値を定義
const user = {
name: "Michael Jakson",
age: 50
};
return (
// Provier の value に定義した値をセット
<UserContext.Provider value={user}>
<h1>useContext</h1>
{/* Consumer を Provider 内部に記載することで value を利用できる */}
<Content />
</UserContext.Provider>
);
};
export default App;
import Name from "./Name";
import Age from "./Age";
// useContext を利用しているため、子コンポーネントへの props バケツリレーが不要!
const Content = () => {
return (
<>
<Name />
<Age />
</>
);
};
export default Content;
import { useContext } from "react";
import { UserContext } from "./App";
const Name = () => {
const { name } = useContext(UserContext);
return <p>Name: {name}</p>;
};
export default Name;
useCallback
useCallback()
で関数をメモ化することでコンポーネントの再レンダリング時に関数の不要な再生性を防ぐ。
親コンポーネントから渡される props に変更がない場合にラップしたコンポーネントの再レンダリングをスキップすることができる memo()
と組み合わせて利用することで、不要なレンダリングを抑制することができる。
メモ化: 同じ結果を返す処理の結果を保持しておき、2回目以降の処理時は都度計算するのではなく保持した値を参照すること。
import { useCallback, useState } from "react";
import Reset from "./Reset";
/**
* useCallback で関数をメモ化することでコンポーネントの再レンダリング時に関数の不要な再生性を防ぎ、
* memo と合わせて利用することでその関数を props として受け取っている子コンポーネントの不要な再レンダリングを防ぐことができる。
*
* memo は親コンポーネントから渡される props に変更がない場合に
* コンポーネントの再レンダリングをスキップすることができる。
*
* ここでは resetCount() を useCallback でラップし、
* memo() でメモ化したResetコンポーネントに渡して再レンダリングを抑制する。
*/
const App = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
// increment 時に state が更新され再レンダリングが走るが、
// resetCount() は useCallback を利用しているため再生成されない。
const resetCount = useCallback(() => {
setCount(0);
}, []);
// 依存配列を指定するとその対象の値が変化した際に再生性が実行される。
// 以下のようにすると count の変化時に resetCount() が再生成され、 useCallback を利用していない場合と同じ挙動になる。
// const resetCount = useCallback(() => {
// setCount(0);
// }, [count]);
// useCallback でラップしていない場合は resetCount() も再生性されてしまう。
// const resetCount = () => setCount(0);
return (
<>
<h1>useCallback</h1>
<p>Count: {count}</p>
<button onClick={increment}>INCREMENT</button>
<Reset resetCount={resetCount} />
</>
);
};
export default App;
import { memo } from "react";
console.log("Reset");
// memo() でラップしmemo化しているため、 props が変更されない場合にレンダリングがスキップされる
const Reset = memo(({ resetCount }) => {
console.log("Render Reset Component!");
return <button onClick={resetCount}>RESET</button>;
});
export default Reset;
useMemo
useMemo()
で関数の結果をメモ化でき、不要な再計算をスキップすることでパフォーマンス向上を図れる。
useCallback()
が関数自体をメモ化するのに対して useMemo()
は関数の結果をメモ化する。
/**
* useMemo で関数による計算結果をメモ化できる。
* 依存配列を指定するとその値に変更があった場合に useMemo が再計算される。
*/
import { useMemo, useState } from "react";
const App = () => {
const [countA, setCountA] = useState(0);
const [countB, setCountB] = useState(0);
const incrementA = () => setCountA(countA + 1);
const incrementB = () => setCountB(countB + 1);
// useMemo でラップし、 countA に変更があった場合のみ doubleCountA を再計算する。
const doubleCountA = useMemo(() => {
console.log("doubleCountA"); // incrementB 実行時は呼ばれない
return countA * 2;
}, [countA]);
return (
<>
<h1>useMemo</h1>
<p>Count A: {countA}</p>
<button onClick={incrementA}>Count Up A</button>
<p>Count B: {countB}</p>
<button onClick={incrementB}>Count Up B</button>
<p>Double Count A: {doubleCountA}</p>
</>
);
};
export default App;
useMemo + memo
useMemo()
で関数の計算結果をメモ化し、 メモ化したコンポーネントに props で渡すことで不要な再レンダリングをスキップする。
/**
* useMemo で関数による計算結果をメモ化して
* memo化した子コンポーネント Count に props として渡すことで
* 不要な再計算&再レンダリングを抑制することができる。
*/
import { useMemo, useState } from "react";
import Count from "./Count";
const App = () => {
const [countA, setCountA] = useState(0);
const [countB, setCountB] = useState(0);
const incrementA = () => setCountA(countA + 1);
const incrementB = () => setCountB(countB + 1);
// useMemo でラップし、 countA に変化がない場合に doubleCountA が再計算されないようにする。
const doubleCountA = useMemo(() => {
return countA * 2;
}, [countA]);
return (
<>
<h1>useMemo + memo</h1>
<p>Count One:{countA}</p>
<button onClick={incrementA}>INCREMENT A</button>
<p>Count Two:{countB}</p>
<button onClick={incrementB}>INCREMENT B</button>
<Count doubleCountA={doubleCountA} />
</>
);
};
export default App;
import { memo } from "react";
// memo でラップし、 props(doubleCountA) に変化がない場合の再レンダリングを抑制する。
const Count = memo(({ doubleCountA }) => {
console.log("Render Count Component!");
return <p>Double Count A: {doubleCountA}</p>;
});
export default Count;
customHook
use
から始まる名前でカスタムフック(自分独自のフック)を作成することで、
コンポーネントからロジックを抽出して再利用可能な関数を作成できる。
ここではサンプルとして画面サイズ情報取得・更新ロジックを共通化して切り出す。
import { useResize } from "./useResize";
const App = () => {
// カスタムフック useResize を呼び出す
const windowSize = useResize();
return (
<>
<h1>コンテンツ領域のサイズ</h1>
<p>ブラウザのコンテンツ領域の幅: {windowSize.width}px</p>
<p>ブラウザのコンテンツ領域の高さ: {windowSize.height}px</p>
</>
);
};
export default App;
import { useEffect, useState } from "react";
/**
* カスタムフックを作成し、画面サイズ情報取得・更新ロジックを共通化して切り出す。
* 基本的にカスタムフックの命名は use から始まるようにする。
*/
export const useResize = () => {
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
}
handleResize();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowSize;
};