はじめに
前回、Hooksとは・フックのルール・useMemo・useCallbackについて学習した記録をまとめました。
今回はその続きで、
- useRef
- useLayoutEffect
- カスタムHooksとは
についてまとめていけたらと思っています。
対 象 読 者
未経験でエンジニアになった方へ
この記事で学べること
- 紹介したフックについて
- カスタムフックについて
Hooksの紹介(前回の続き)
useRef
レンダー時には不要な値を参照するための Reactのフック
AIにわかりやすく言い換えてもらうと「こっそりメモできる、見えないポケット」とのこと。
useStateに値を書き込むとReactは値が変わったと気づいて再描画するけど、refに書いた場合、Reactに気づかれず、値をこっそり持っておきたいときや、画面の部品そのもの(ボタンや入力欄など)に直接さわりたいときに使います。
どんな時に使うの?
// 基本的な使い方
// 変数名 = useRef(初期値)
const ref = useRef(initialValue)
- DOM要素に直接さわりたいとき
従来のJavaScript開発では、DOM要素にアクセスして操作するには、
getElementById
querySelector
getElementsByClassName
のようなメソッドを使用して、ドキュメントから特定の要素を選択する必要がありました。
しかし、useRefを使うと、この要素を探しにいく作業が不要になります。
どういうことかというと、JSXの中でref={inputRef}と書いておくだけで、Reactがその要素を画面に描画したタイミングで、実際のDOM要素そのものを自動的にinputRef.currentに入れてくれて、要素そのものが手に入っている状態になっています。
この状態を直接さわれるといういいます。
例
// document.querySelector を使っての実装
function TextInput() {
const handleClick = () => {
document.querySelector('input').focus(); // ページ内で最初に見つかった<input>を探す
};
return (
<>
<input />
<button onClick={handleClick}>フォーカスする</button>
</>
);
}
function App() {
return (
<>
<TextInput />
<TextInput />
</>
);
}
document.querySelector('input')はページ全体の中で最初に見つかったを探すので、どちらのボタンを押しても、常に1つ目の入力欄にしかフォーカスが当たりません。2つ目のTextInputのボタンを押したのに、1つ目の入力欄が反応してしまう、というバグになります。
// useRefを使っての実装
function TextInput({ label }) {
// TextInputが呼ばれるたびに、空のオブジェクトを作成
const inputRef = useRef(null);
const handleClick = () => {
// <input>要素に対して.focus()を呼び出す
inputRef.current.focus();
};
return (
<div>
<p>{label}</p>
{/* ref={inputRef}で、実際の<input>要素がinputRef.currentに入る */}
<input ref={inputRef} />
<button onClick={handleClick}>フォーカスする</button>
</div>
);
}
function App() {
return (
<>
{/* 1つ目のTextInput呼び出し → このコンポーネント専用のinputRefが1つ作られる */}
<TextInput label="1つ目のTextInput" />
{/* 2つ目のTextInput呼び出し → 別の専用のinputRefがもう1つ作られる */}
<TextInput label="2つ目のTextInput" />
</>
);
}
useRefをTextInputの中で呼んでいるので、TextInputが呼ばれるたびに、それぞれ専用のinputRefが作られます。
- こっそり値を持っておきたいとき(値が変わっても画面を更新しなくていい場合)
- 例えばタイマーで ボタンをクリックした回数を保持しておきたいとき
- 押した時は、画面に何も表示はされない(再レンダリングされない)
- でも
countRef.currentにちゃんと回数が溜まっていて、今までの回数を見るボタンを押した瞬間だけ、alertでその値を確認できる。
DOMとは
HTMLやXMLでマークアップされた文書を、JavaScriptなどのスクリプトからアクセス・操作できるようにするAPI
Reactにおけるstateとrefの違い
Reactにおけるstateとrefは、どちらもコンポーネント内でデータを保持するために使われますが、それぞれ異なる役割と使い方があります。
-
useState
- 前回の記事でも
useStateは関数コンポーネントの状態を保持したり、更新したりするためのフックと説明しました。つまり、stateへの更新があると再レンダリングします。
- 前回の記事でも
-
useRef
- 再レンダリングに影響を与えないデータを保持するために使う
- 直接UIに表示されないデータの管理なので、再レンダリングされません。
よく例えでタイマーアプリが挙げられます。
カウントアップする数値がuseState、開始ボタンを押したときに発行される、タイマーを止めるためのID(setIntervalの戻り値)がuseRefになります。
とはいえ、これから実務ではuseStateを使いがちになりそうだと感じました。
useRefの使い所をしっかり考えて使い分けできるようになっていきたいです。
注意点
- ref.currentを書き換えても、画面は再レンダリングされない
useStateと違って、ref.currentの値をどれだけ変えても、Reactは変わったと気づかないため、画面の表示を更新したい場合はuseStateを使う必要があります。 - レンダー中にref.currentを読み書きしない
公式でも、初期化のとき以外はレンダー中にref.currentを読み書きしてはいけないと明記されています。レンダリングの途中で値を書き換えてしまうと、コンポーネントの動きが予測しづらくなるためです。 - useEffectの第二引数にいれても、変更を検知しない
refオブジェクト自体は再レンダリングされても常に同じものなので、依存配列に入れても変わったと判定されず、.currentの変更をuseEffectで検知することはできません。
useLayoutEffect
useEffect の一種ですが、ブラウザが画面を再描画する前に実行されます。
どんな時に使うの?
// 基本的な使い方
useLayoutEffect(setup, dependencies?)
- DOMのサイズや位置の測定
- 要素の実際の大きさや位置を知りたいとき
- スクロール位置の制御
- 画面のスクロール位置を、ユーザーに見せる前に調整したいとき
- 同期的なスタイル適用
- 見た目にすぐ影響するスタイル(色・位置・サイズなど)を、ちらつきなく反映したいとき
useLayoutEffectとuseEffectの違い
-
useEffect
- DOMが更新され、ブラウザが画面を描画した後に実行されるフック
-
useLayoutEffect
- DOMが更新され、ブラウザが画面を描画する前に、同期的に実行されるフック
基本は公式でも記載あるように、useEffect を使うのがおすすめですが、どんなときに使うの?に記載あるようにユーザーに間違った画面を見せないようにしたいときにuseLayoutEffectを使うのがいいのかなと思いました。
初めてuseLayoutEffectを使ったときは、こっちの方がユーザー体験いいのでは?と思いましたが、注意点を見るとパフォーマンスを低下させる可能性があるため、むやみやたらに使わないように気をつけていきたいです。
注意点
- useLayoutEffect はパフォーマンスを低下させる可能性があります。可能な限り useEffect を使用することを推奨 と公式でも記載があります
- ブラウザの画面描画を一旦止めてから同期的に処理を行うため、多用すると画面の表示が遅くなる原因になるため公式でも落とし穴として記載しているようです
カスタムHooksとは
React には useState、useContext、useEffect など複数の組み込みフックが存在します。
しかし、データの取得やユーザのオンライン状態の監視、チャットルームへの接続など、より特化した目的のためのフックが欲しいこともあります。React にこれらのフックはありませんが、アプリケーションの要求に合わせて独自のフックを作成することが可能です。と公式に記載があります。
つまりいくつかのHooksを組み合わせて自分専用のフックを作れるのがカスタムHooksです。
カスタムフックのメリット
- 同じ処理を1つに集約できるのでコンポーネントの複雑化を防ぐ
- コンポーネントのViewとロジックを分離できる
- ロジックの再利用ができる
- コンポーネントが読みやすくなる
ルール
- カスタムフックは内部で
useStateやuseEffectなどの組み込みフックを呼び出す関数 - カスタムフックの名前は必ず
useで始める
使い方
useから始まる関数を作り、その中で他のフック(useStateやuseEffectなど)を呼び出すだけです。
といえば簡単に聞こえますが、未経験にはどこでカスタムフック使うのがいいのか、まだ理解しきれませんでした。
例えば、今のウィンドウの幅を取得する処理をカスタムフックにするとこうなります。
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
// ウィンドウのリサイズが起きるたびに実行したい処理
const handleResize = () => setWidth(window.innerWidth);
// ブラウザにリサイズが起きたらhandleResizeを呼ぶと登録する
window.addEventListener('resize', handleResize);
// コンポーネントが消えるときに、登録した監視を解除する
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
使う側のコンポーネント
function MyComponent() {
const width = useWindowWidth();
return <p>画面の幅: {width}px</p>;
}
useStateやuseEffectの中身の実装はuseWindowWidthの中に隠れているので、MyComponent側は幅がほしければuseWindowWidth()を呼ぶだけでいいというシンプルな形になります。
### 注意点
- カスタムフックの中でもフックのルールは適用される
- 状態そのものは共有されない
-
useWindowWidthを2つの別々のコンポーネントで呼び出しても、2つは別々の独立した状態を持つ。 - カスタムフックが再利用しているのは処理の書き方であって、状態そのものではない
-
その他(おまけ枠)
Next.jsにも専用のパッケージがある。
これらはReact自体には存在しない機能で、Nwxt.jsが独自に持っているルーティングにアクセスするためのものですが、ここについては今回全然勉強できませんでした。
- useRouter
- ページ遷移(router.push()で別ページに飛ばす、など)
- usePathname
- 今表示しているURLのパス(例: /company/123 のような部分)を取得
- useSearchParams
- URLのクエリパラメータ(?page=2 みたいな部分)を取得
まとめ
まだまだReactにはたくさんのフックがありますが、今回は2つの記事に分けて、4つのフックに絞って紹介しました。
また他のフックについても、勉強していきたいと思っているので、まとめていきたいと思います。
前回の記事はこちら
未経験エンジニアがReact Hooksを一から勉強してみた【フックの基本〜useMemo・useCallback編】

