はじめに
新しいReactプロジェクトを始めるたびに、useLocalStorageやuseDebounce、useWindowSizeといった定番のカスタムフックを一から書き直していませんか。ロジック自体はシンプルでも、SSR対応やイベントリスナーの後始末まで含めるとちょっとした手間がかかりますし、プロジェクトごとに微妙に実装がずれてしまうこともあります。
そんな「毎回車輪の再発明」から解放してくれるのが、Usehooks-Tsです。TypeScriptで書かれた汎用カスタムフック集で、必要なものだけをインポートしてすぐに使えます。この記事では、Usehooks-Tsの特徴からインストール方法、実践的な使い方までを解説します。
とはいえ、読むより触った方が早いと思います。useLocalStorageが実際に値を保存する様子を、実際に動くサンプルで見られます。先に挙動を見たい方はこちらからどうぞ。
Usehooks-Tsとは
Usehooks-Tsは、Julien Caron氏が中心となって開発しているReact向けカスタムフックライブラリです。「DRY(Don't Repeat Yourself)」の原則に基づき、状態管理・ストレージ操作・イベント処理・DOM操作など、実務で頻繁に必要になるフックを35種類以上まとめて提供しています。GitHub上で7,800以上のスターを獲得しており、多くのプロジェクトで採用されている定番ライブラリです。
主な特徴
- 完全なTypeScript対応 - すべてのフックが型定義付きで提供され、ジェネリクスによる型推論も効きます
- ツリーシェイキング対応 - 使用するフックのみがバンドルに含まれるため、不要なコードを持ち込みません
- 依存関係が最小限 - 軽量な実装で、余計なライブラリを増やしません
- SSR対応 -
useLocalStorageなど一部のフックはサーバーサイドレンダリング環境も考慮した設計になっています - 豊富なドキュメント - 公式サイトに各フックの使用例とAPIシグネチャが整理されています
インストール
npm、yarn、pnpmのいずれでもインストール可能です。
npm install usehooks-ts
yarn add usehooks-ts
pnpm add usehooks-ts
Usehooks-Tsのサンプルを動かす
Usehooks-Tsの実力を一番手っ取り早く感じられるのは、useLocalStorageフックです。useStateとまったく同じ形の[value, setValue, remove]という配列を返しつつ、値の保存先がReactのメモリではなくブラウザのlocalStorageになる、という違いだけがあります。要点だけを抜き出すと、次のような書き方になります。
import { useLocalStorage } from 'usehooks-ts'
function NameForm() {
const [name, setName, removeName] = useLocalStorage('display-name', '')
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<p>こんにちは、{name || '名無し'}さん</p>
<button onClick={removeName}>保存データを削除</button>
</div>
)
}
実際に動かせるものが下です。入力欄に名前を入れてみてください。setNameが呼ばれるたびにlocalStorageのdisplay-nameキーへ書き込まれるため、このプレビューをリロードしても直前の入力が復元されます。「保存データを削除」ボタンを押すとremoveNameが実行され、キーごと消えて初期値の空文字に戻ります。
useLocalStorageの第一引数がストレージキー、第二引数が初期値です。キー名を'display-name'から'nickname'のように変えると、まったく別のスロットとして独立して保存されるようになるので、同じページで複数の値を扱いたいときはキーを分けるだけで済みます。
基本的な使い方
もっともよく使われるフックの一つ、useLocalStorageを見てみましょう。値の保存・更新・削除を、useStateとほぼ同じ感覚で扱えます。
import { useLocalStorage } from 'usehooks-ts'
function Counter() {
const [count, setCount, removeCount] = useLocalStorage('count', 0)
return (
<div>
<p>現在のカウント: {count}</p>
<button onClick={() => setCount((prev) => prev + 1)}>増やす</button>
<button onClick={() => setCount(0)}>リセット</button>
<button onClick={removeCount}>保存データを削除</button>
</div>
)
}
setCountには値そのものだけでなく、useStateと同様に更新関数も渡せます。ページをリロードしても値がlocalStorageに残り続けるのがポイントです。
実践的なユースケース
ダークモード切り替え
useDarkModeを使えば、OSの設定を尊重しつつユーザー自身での切り替えにも対応したダークモードをすぐに実装できます。
import { useDarkMode } from 'usehooks-ts'
function ThemeToggle() {
const { isDarkMode, toggle } = useDarkMode()
return (
<button onClick={toggle}>
{isDarkMode ? 'ライトモードに切り替え' : 'ダークモードに切り替え'}
</button>
)
}
useDarkModeは内部でuseLocalStorageとOSのprefers-color-schemeメディアクエリを組み合わせており、isDarkModeという真偽値とtoggle関数だけでテーマ切り替えが完結する点がポイントです。実際に背景色が切り替わる様子まで含めたサンプルが以下です。ボタンを押すとisDarkModeが反転し、それに応じて背景とテキストの色が入れ替わります。
isDarkModeの初期値はブラウザのprefers-color-scheme設定を尊重しつつ、一度toggleすると結果がlocalStorageに保存されるため、次回訪問時も選んだテーマがそのまま復元されます。backgroundの三項演算子をisDarkMode ? '#000' : '#f5f5f5'のように書き換えれば、そのまま自分のUIの配色に流用できます。
検索入力のデバウンス処理
入力のたびにAPIを叩いてしまうと無駄なリクエストが増えてしまいます。useDebounceCallbackを使えば、入力が落ち着いたタイミングだけ処理を実行できます。
import { useState } from 'react'
import { useDebounceCallback } from 'usehooks-ts'
function SearchBox() {
const [keyword, setKeyword] = useState('')
const debouncedSearch = useDebounceCallback((value: string) => {
console.log('検索実行:', value)
// ここで検索APIを呼び出す
}, 500)
return (
<input
type="text"
placeholder="キーワードを入力"
onChange={(e) => {
setKeyword(e.target.value)
debouncedSearch(e.target.value)
}}
value={keyword}
/>
)
}
useDebounceCallbackは第一引数に渡した関数を、第二引数のミリ秒だけ入力が止まってから実行するようラップしてくれるフックです。実際に入力しながら、実行タイミングがどれだけ遅れるかを確認できるサンプルが以下です。文字を打っている間は何も起きず、500ミリ秒手を止めた瞬間だけ「検索実行」のログが1件追加されます。
第二引数の500を1500のように大きくすると、実行までの猶予がさらに長くなり、タイピング中にログが増えにくくなるのが体感できます。実務では、このdebouncedSearchの中でAPIリクエストを呼び出すことで、1文字打つたびに通信が走るのを防げます。
要素の画面内表示を検知する
無限スクロールや遅延読み込みでよく使う「要素が画面内に入ったか」の判定も、useIntersectionObserverで簡単に実現できます。
import { useRef } from 'react'
import { useIntersectionObserver } from 'usehooks-ts'
function LazySection() {
const ref = useRef<HTMLDivElement>(null)
const entry = useIntersectionObserver(ref, {})
const isVisible = !!entry?.isIntersecting
return (
<div ref={ref}>
{isVisible ? '画面内に表示されました!' : '読み込み待機中...'}
</div>
)
}
現行バージョンのuseIntersectionObserverは{ ref, isIntersecting }を返す形になっており、返ってきたrefを監視したい要素にそのまま渡すだけで使えます。thresholdオプションで「要素の何割が見えたらtrueにするか」を指定できます。下のサンプルは、灰色の余白をスクロールして下の「監視対象の要素」が画面に50%以上入ると背景が緑色に変わります。
threshold: 0.5を1に変えると、要素が完全に画面内へ入るまでisIntersectingがtrueにならなくなります。この仕組みは無限スクロールでの追加読み込みや、画像の遅延読み込みのトリガーとしてそのまま応用できます。
まとめ
Usehooks-Tsは、Reactアプリケーション開発で「あるある」な処理を型安全なフックとしてまとめてくれる便利なライブラリです。localStorageの管理やダークモード、デバウンス処理、Intersection Observerといった定番の実装を自作する手間が省け、コードの見通しも良くなります。ツリーシェイキングにも対応しているため、必要なフックだけを取り込めばバンドルサイズを気にする必要もありません。
まずは公式ドキュメントで用意されているフックの一覧をざっと眺めてみて、自分のプロジェクトで使えそうなものから取り入れてみてはいかがでしょうか。
