React hooksを網羅的に解説
今回はReactのhooksについて、網羅的に解説していきます。
Reactを学習し始めたけど、まだイマイチ理解していないという方はぜひ参考にしてください。
そもそもhooksとは
そもそもhooksとは何なのかを解説していきます。
まず、hookとはReact内部にhook into(接続)するための関数になります。
要は、この関数を使うことで、Reactの便利な機能を使えるようになるわけです。
そして、hookにはuseStateやuseEffectなどのhookが複数あるので、複数形のhooksと呼ばれています。
つまり本記事は、Reactで使える便利な機能を網羅的に紹介していく、という趣旨になります。
useState
useStateはコンポーネントの状態を管理するためのhookになります。
基本的な使い方は次のとおりです。
const [num,setNum] = useState(0)
このように初期値を渡すことで、stateとstateの更新用関数を取得することができます。
詳しくはこちらの記事で紹介しているので、参考にどうぞ。
useRef
次に紹介するのが、useRefになります。
こちらは主に2つの用途があります。
1つ目がDOMの情報を取得でき、操作が可能となる点です。
まずはコードを見た方が早いと思うので、次のとおりです。
const Input = () => {
const inputElement = useRef(null)
const handleClick = () => {
inputElement.current.focus();
};
return(
<>
<input ref={inputElement} type="text" />
<button onClick={handleClick}>フォーカス</button>
</>
)
}
このように、別のDOMから別のDOMを操作できるようになります。
2つ目が再レンダリングを発生させずに、値を保持できるという点になります。
この機能があるので、1つ目の機能が実現できてるわけです。
具体的にコードで見ると次のとおりです。
const Test = () => {
const num = useRef(100)
const onClickBtn = () => {
num.current = 200
// {current: 200}
console.log(num)
}
// {current: 100}
console.log(num)
return(
<>
<button onClick={onClickBtn}>インクリメント</button>
<div>{num.current}</div>
</>
)
}
useRefに初期値を渡すことで、refのcurrentプロパティに初期値が入った状態で渡ってきます。
インクリメントボタンを押した場合、numの値は更新されますが、再描画は発生しません。
なので、stateの更新などで再描画されたタイミングで、更新後のnumの値が表示されます。
どこで使うのかと思うかもですが、再描画を発生させずに値を保持したいということは割とよくあるので、覚えておくと良いでしょう。
useRefの注意点
useRefには、一つ注意点があります。
それはref属性は特別な属性なので、propsとしてrefを渡すことはできないという点です。
例えば、次のようにした場合はエラーにはなりませんが、warningが出てしまいます。
<Test ref={inputElement} />
こちらの解決策は簡単で、propsの名前を変えれば良いだけです。
<Test refProp={inputElement} />
また、以下のようにfowardRefという関数を使って、propsと別でrefを渡すことも可能です。
const Test = forwardRef((props,ref) => {
<input ref={inputElement} type="text" />
….
})
const Input = () => {
const inputElement = useRef(null)
const handleClick = () => {
inputElement.current.focus();
};
return(
<>
<button onClick={handleClick}>フォーカス</button>
<Test ref={inputElement} />
</>
)
}
けれど、このままだと親コンポーネントから好きなようにrefを操作できてしまいます。
なので、開発者の意図しないコードを書かれてしまう可能性があります。
そんな時は、useImperativeHandleでrefの挙動を制限することが可能です。
ただ、そもそもrefをコンポーネント間で受け渡しするのは、コンポーネントが密結合になるので非推奨とされています。
なので、余程の理由がない限りは使わない方が良いです。
useReducer
次に、useReducerを解説していきます。
こちらのhookはuseStateと似ていますが、更新用関数も合わせて定義することができます。
こうすることで機能を制限できるので、チーム開発をしている時などはバグの発生を防ぐことができます。
コードで見ると次の通りです。
const reducerFunc = (countState, action)=> {
switch (action){
case 'increment':
return countState + 1
case 'decrement':
return countState - 1
case 'reset':
return initialState
default:
return countState
}
}
const Counter = (props) => {
const [count, dispatch] = useReducer(reducerFunc, initialState)
return (
<>
<h2>カウント:{count}</h2>
<button onClick={()=>dispatch('increment')}>increment</button>
<button onClick={()=>dispatch('decrement')}>decrement</button>
<button onClick={()=>dispatch('reset')}>reset</button>
</>
)
}
まず、useReducerの第一引数に更新用関数、第二引数に初期値を設定します。
今回のように複数の処理をしたい時は、引数を元に処理を条件分岐させればOKです。
更新用関数には、第一引数に現在のstate、第二引数が呼び出し側で引数として入れたactionが渡ってきます。
なので、それを元にした関数を作っておけばOKです。
useContext
次に、useContextについて解説していきます。
こちらのhookを使うことで、グローバルなstateを定義することができます。
つまり、どこからでもstateの利用が可能となります。
コードで見ると次の通りになります。
// 初期値を設定可能
const BirthDayPeriodContext =
createContext();
const App =(props) => {
const [globalBirthDay] = useState(817);
return (
<BirthDayPeriodContext.Provider value={[globalBirthDay,setGlobalBirthDay]}>
<div id="test">
…
<Counter />
</div>
</BirthDayPeriodContext.Provider>
);
}
const Counter = (props) => {
const [searchDatePeriod, setSearchDatePeriod] = useContext(
BirthDayPeriodContext,
);
console.log(searchDatePeriod)
return (
<>
…
</>
)
}
まず、createContextでコンテキストを作って、グローバルな値を設定することが可能です。
そしてstateを設定したい場合は、Providerで囲ってvalueにstateを渡せばOKです。
そうすることで、どこからでもuseContextを使って、グローバルstateを取得することができます。
ちなみにstateではなく、単に定数などを共通で使用したい場合は、createContextの初期値としてその値を渡せば良いだけなので、Providerで囲む必要はないです。
useContextの注意点
useContextを使用する際に、注意点が1つあります。
それはグローバルstateが更新されると、そのstateを使ってるコンポーネント全てが再レンダリングすることです。
一見問題ないように見えますが、今回の例で言うと、CounterコンポーネントではsetGlobalBirthDayを使っていないのに、setGlobalBirthDayが更新されると、Counterコンポーネントも再レンダリングされてしまいます。
つまり、不要なレンダリングが発生するわけです。
そういった場合は、contextを2つに分けるなどの対策をすることで、不要な再レンダリングを防ぐことができます。
ただグローバルstateが増えてくると、このように管理が大変になるので、そもそもグローバルstateをあまり使わないというのがベストプラクティスとなります。
または、複雑なアプリケーションの場合は、reduxの導入を検討してみると良いでしょう。
useEffect
次に、useEffectについて解説していきます。
こちらは、コンポーネントの副作用を扱うためのhookになります。
さらに分かりやすい言い方で言うと、コンポーネントの描画後に関数を実行することができます。
例えば、コンポーネントの描画後にAPIでデータを取ってきて、それをstateに設定するなどの用途で使われます。
基本的な書き方は次の通りです。
useEffect(() => {
console.log(‘useEffectが実行されました!')
},[])
まず、第一引数に実行したい関数を登録します。
そして、第二引数に依存配列を設定します。
依存配列について詳しく解説すると、その値が更新される毎に関数が実行されるようになります。
ちなみに、今回のように空の場合は、最初1回目のレンダリング時にのみ関数が実行されます。
また、useEffectは複数登録することができます。
そして、それらは登録順に実行されます。
また、useLayoutEffectという似たhookもあります。
こちらはコンポーネントのレンダリング前に実行されます。
なので画面に初期値を表示したくない時などに利用します。
ただ、描画前に重い処理を書いたりすると、何も表示されない時間が増えるのでUXが悪くなります。
なので、あまり使っている現場はないかと思います。
クリーンアップ関数
最後に、クリーンアップ関数について解説します。
こちらは、コンポーネントのアンマウント時に実行させる関数を登録できる機能になります。
具体的には、useEffectの第一引数の関数内のreturn以降の処理が、クリーンアップ関数として実行されます。
useEffect(() => {
console.log("hello")
return () => {
console.log("bye")
}
})
ちなみに、このクリンアップ関数は再描画後も最初に呼ばれ、次にuseEffectの処理が行われます。
つまりこの例で言うと、再描画時は”bye”⇨”hello”の順でコンソールに出力されます。
そして、アンマウントされた時は、"bye"のみが出力されます。
メモ化
次に、メモ化に使えるhookを紹介していきます。
メモ化とは、プログラム高速化させる技法の1つです。
具体的には関数の結果などを記憶しておいて、2回目以降の処理ではその関数を実行させずにする技法になります。
そしてそれを実現させるためのhookが、Reactには3つあります。
なので、今回はそれらを解説していきます。
memo
まず、memoというhookについて解説していきます。
こちらはコンポーネントをメモ化できる機能になります。
具体的には、不要な再レンダリングを防ぐことができます。
コンポーネントは、次の3つの時に再レンダリングが発生します。
- Propsが更新された時
- Stateが更新された時
- 親コンポーネントが更新された時
Props,Stateが更新された時は良いのですが、もしどちらも変わらずに親コンポーネントが更新された時を考えてみましょう。
画面は何も変わらないのに、再レンダリングが発生することになってしまいます。
つまり、不要な再レンダリングが発生してしまうわけです。
そして、memoを使うことでそれを防ぐことができます。
具体的な使い方は、次の通りです。
const Input = memo(() => {
console.log("render")
const inputElement = useRef(null)
const handleClick = () => {
inputElement.current.focus();
};
return(
<>
<input ref={inputElement} type="text" />
<button onClick={handleClick}>フォーカス</button>
<Test ref={inputElement} />
</>
)
})
memoでコンポーネントを囲むことで、コンポーネントの不要な再レンダリング防げます。
そして意外と知られていませんが、第2引数で比較の関数を渡せます。
ただほぼ使わないので、あまり気にしなくてOKです。
useCallback
次に紹介するのが、useCallbackになります。
こちらは関数をmemo化するために使います。
なんでそんなのが必要なのかと思うかもなので、次の例を見てください。
const App =(props) => {
const [toggle,setToggle] = useState(false)
const hello = () => {
console.log("hello")
}
return (
<div id="test">
<button onClick={() => setToggle(!toggle)}>切り替え</button>
{toggle && <ModalPodal />}
<Input hello={hello} />
</div>
);
}
こちらではコンポーネントをメモ化しているのにも関わらず、切り替えのボタンを押すと、再レンダリングが発生してしまいます。
これはコールバック関数は、毎回違う関数を作成するためです。
なので、関数のpropsが前回と変化したとReactが検知し、再レンダリングが発生しているわけです。
そしてこの挙動は、useCallbackで防ぐことができます。
const hello = useCallback(() => {
console.log("hello")
},[])
第二引数には、useEffectと同じように依存配列を渡すことができます。
依存配列のいずれかの値が変わった時は、新たな関数を作成するという挙動になります。
useMemo
最後に紹介するのが、useMemoになります。
このuseMemoは、あらゆる値をメモかできます。
基本的にこのuseMemoは、変数のメモ化に使われます。
例えば、複雑な計算などをしていて、算出に時間がかかる変数をメモ化することでパフォーマンスが向上します。
ただ、useMemoも処理コストがかかるので、本当に重い処理以外に使うと逆効果になることに注意しましょう。
使い方だけ紹介しておくと次の通りです。
const today = useMemo(() => new Date(),[])
第一引数に値を返すコールバック関数を渡し、第二引数はuseCallbackと同じように依存配列を渡します。
依存配列の役割もuseCallbackと同じです。
カスタムhook
最後に、カスタムhookについて解説していきます。
カスタムhookとは、hookを呼び出せる関数のことです。
つまり、カスタムフックとはただの関数です。
そして、そのカスタムフックの中では、hooksを使うことができます。
そんなの何の意味があるのかと思うかもですが、こうすることで処理を共通化できます。
また、ファイルごとに処理を分けられるため、関心の分離ができ、可読性も向上します。
実際のコードを見た方が分かりやすいと思うので、次の通りです。
const useCounter = () => {
const [count, setCount] = useState(0)
const onClickButton = () =>{
setCount(count + 1)
}
return {count, onClickButton}
}
const Counter2 = (props) => {
const {count, onClickButton} = useCounter()
return (
<>
<h2>カウント:{count}</h2>
<button onClick={onClickButton}>increment</button>
</>
)
}
ちなみに、このカスタムフックはuseという文字で始めるという決まりがあります。
まとめ
今回は、Reactのhooksについて網羅的に解説してきました。
ぜひこの記事を参考に、Reactの理解を深めてください。
宣伝
0からエンジニアになるためのノウハウをブログで発信しています。
また、YouTubeでの動画解説も始めました。
インスタの発信も細々とやっています。
興味がある方は、ぜひリンクをクリックして確認してみてください!
Discussion