追加のフック
React組み込みのフックのうち、追加のフックを試していきます。
- useReducer
- useRef
- useCallback
- useMemo
useReducer
状態管理のためのフックです。
useStateと似た機能です。
useStateとuseReducer
useStateはuseReducerで実装されています。
(=useStateでできることは全てuseReducerでもできるということになります)
useStateとuseReducerの比較
useState | useReducer | |
---|---|---|
扱えるstateのtype | 数値、文字列、真偽値 | オブジェクト、配列 |
関連するstateの取り扱い | 不可 | 複数可能 |
ローカル/グローバル | ローカル | グローバル(useContext()を併用) |
キーワード | 説明 |
---|---|
state | 現在の値 |
reducer | stateを更新するための関数(stateとactionを受け取って、stateを返す) |
dispatch | reducerを実行するための呼び出し関数 |
action
何をするのかを示すオブジェクトです。
typeプロパティと値のプロパティで構成します。
{
type:increment,
payload:0
}
const recducer関数名 = (state, action)=> {
switch (action){
case アクションのtype1:
return {処理A}
case アクションのtype2:
return {処理B}
case アクションのtype3:
return {処理C}
default:
return state
}
}
const [state, dispatch] = useReducer(reducer関数名,'初期値')
useReducer()を使ってカウンターを作ってみる
stateが単数の場合
//useReducerをimport
import React, {useReducer} from 'react'
//counterの初期値を0に設定
const initialState = 0
//reducer関数を作成(countStateとactionを渡して、処理後のcountStateを返すように実装する)
const reducerFunc = (countState, action)=> {
//reducer関数にincrement、increment、reset処理を書く
//どの処理を渡すかはactionを渡すことによって判断する
switch (action){
case 'increment':
return countState + 1
case 'decrement':
return countState - 1
case 'reset':
return initialState
default:
return countState
}
}
//コンポーネントCounterを作成
const Counter = () => {
//作成したreducerFunc関数とcountStateをuseReducerに渡す
//useReducerで変数名、変更に使う手法、reducer関数、初期値を紐付けする
//useStateと同じく、[値、変更する手法]=useReducer(reducerで使う関数,初期値)初期値が無いと初期表示がブランクになってしまう
//使う時は、変更する手法('reducerの処理の分岐の判定に使うキーワード')
const [count, dispatch] = useReducer(reducerFunc, initialState)
return (
<>
{/* カウント数の表示 */}
<h2>{count}</h2>
<div>
{/* それぞれのactionを実行するボタン */}
<button onClick={()=>dispatch('increment')}>+1</button>
<button onClick={()=>dispatch('decrement')}>-1</button>
<button onClick={()=>dispatch('reset')}>初期化</button>
</div>
</>
)
}
export default Counter
// ReactからcreateContextとuseStateをimport
import React from 'react'
//Conterコンポーネントをインポート
import Counter from './Counter'
function App() {
return (
<div className='App'>
<Counter />
</div>
)
}
export default App
stateが複数の場合
1つのstateが扱うデータを複数にして、増減の値も変えてみます。
//useReducerをimport
import React, {useReducer} from 'react'
//counterの初期値を0に設定
//2つのcountStateを作るので、それぞれのinitialStateを設定
const initialState ={
firstCounter: 0,
secondCounter: 0
}
//reducer関数を作成
//countStateとactionを渡して、新しいcountStateを返す
const reduceFunction = (countState, action)=> {
//switch文のactionをaction.typeに変更(typeとvalueを渡したいのでオブジェクトにして渡す)
//複数のcounterStateを持っている場合は、更新前のcounterStateを展開し、オブジェクトのマージを行う
switch (action.type){
case 'increment1':
return {...countState, firstCounter: countState.firstCounter + action.value}
case 'decrement1':
return {...countState, firstCounter: countState.firstCounter - action.value}
case 'increment2':
return {...countState, secondCounter: countState.secondCounter + action.value}
case 'decrement2':
return {...countState, secondCounter: countState.secondCounter - action.value}
case 'reset1':
return {...countState, firstCounter: initialState.firstCounter}
case 'reset2':
return {...countState, secondCounter: initialState.secondCounter}
default:
return countState
}
}
const Counter2 = () => {
//作成したreducerFunc関数とcountStateをuseReducerに渡す
//useReducerで変数名、変更に使う手法、reducer関数、初期値を紐付けする
//useStateと同じく、[値、変更する手法]=useReducer(reducerで使う関数,初期値)初期値が無いと初期表示がブランクになってしまう
//使う時は、変更する手法('reducerの処理の分岐の判定に使うキーワード')
const [count, dispatch] = useReducer(reduceFunction, initialState)
return (
<>
{/* カウント数の表示 */}
<h2>カウント1:{count.firstCounter}</h2>
{/* それぞれのactionを実行するボタン */}
{/*dispatchで渡すactionをオブジェクトに変更して、typeとvalueを設定*/}
<button onClick={()=>dispatch({type: 'increment1', value: 1})}>+1</button>
<button onClick={()=>dispatch({type: 'decrement1', value: 1})}>-1</button>
<button onClick={()=>dispatch({type: 'reset1'})}>初期化</button>
{/* カウント数の表示 */}
<h2>カウント2:{count.secondCounter}</h2>
{/* それぞれのactionを実行するボタン */}
{/*dispatchで渡すactionをオブジェクトに変更して、typeとvalueを設定*/}
<button onClick={()=>dispatch({type: 'increment2', value: 2})}>+2</button>
<button onClick={()=>dispatch({type: 'decrement2', value: 2})}>-2</button>
<button onClick={()=>dispatch({type: 'reset2'})}>初期化</button>
</>
)
}
export default Counter2
// ReactからcreateContextとuseStateをimport
import React from 'react'
//Conter2コンポーネントをインポート
import Counter from './Counter2'
function App() {
return (
<div className='App'>
<Counter2 />
</div>
)
}
export default App
useRef
要素への参照を行うことが出来ます。
useStateのようにコンポーネント内での値を保持することも出来ます。
値を保持する
変数にuseRefを使って .currentプロパティ内に値を保持させられます。
値は変数名.currentで参照できます。
const refObject = useRef(initialValue)
//例
const number = useRef(100); // {current: 100}
console.log(number.current); // 100
DOMを参照する
const inputElement = useRef(null)
//例
<input ref={inputElement} type="text" />
console.log(inputElement); // current: null
import React, {useRef} from 'react'
const RefSample=()=>{
//変数inputElementという参照ポイントを作成
const inputElement = useRef(null);
console.log(inputElement);//{current: null}
console.log(inputElement.current);//null
//関数handleClick
const handleClick = () => {
console.log(inputElement); //{current: input}
console.log(inputElement.current); // <input type="text">
//inputElement.current(=<input type="text">)にフォーカスする
inputElement.current.focus();
};
return (
<>
{/* inputを参照ポイント「inputElement」と紐付ける */}
<input ref={inputElement} type="text" />
{/* ボタンを押すとhandleClickを実行 */}
<button onClick={handleClick}>入力エリアをフォーカスする</button>
</>
);
}
export default RefSample;
useCallback
パフォーマンス低下抑制のためのフックです。
メモ化したコールバック関数を返します。
メモ化
初回の処理を記録しておきます。
依存配列の要素のいずれかが変化した場合のみ、メモ化した値を再計算することで、不要なレンダリングを抑制します。
const callback = useCallback(関数,[依存配列])
Reactコンポーネントの再レンダリング条件
条件を1つでも満たした場合には再レンダリングされます。
条件 | 内容 |
---|---|
propsの更新 | 親の再レンダリングによる無意味なpropsの更新の場合、useCallbackで抑制 |
stateの更新 | レンダリングは必要なので、メモを破棄してレンダリングする |
親コンポーネントが再レンダリング時 | 不要な場合React.memoで抑制 |
入力された文字をカウントする
コンポーネントがレンダリングされるとconsole.logで確認できるようにします。
メモ化していない場合
import React,{useState} from 'react'
import InputElementA from './InputElementA';
import InputElementB from './InputElementB';
import CountA from './CountA';
import CountB from './CountB';
//コンポーネントApp
const App = () => {
//親要素が持つstate変数inputTextAを定義
const [inputTextA, setInputTextA] = useState("");
//親要素が持つstate変数inputTextBを定義
const [inputTextB, setInputTextB] = useState("");
//state関数inputTextAを更新する関数inputTextFunctionAを定義
const inputTextFunctionA=(text)=> {
setInputTextA(text);
}
//state関数inputTextBを更新する関数inputTextFunctionBを定義
const inputTextFunctionB=(text)=> {
setInputTextB(text);
}
return(
<>
{/* inputAに入力された文字列を表示 */}
<CountA>{inputTextA}</CountA>
{/* 子要素に親要素の関数を渡す */}
<InputElementA inputTextFunctionA={e => inputTextFunctionA(e)}/>
{/* inputに入力された文字列を表示 */}
<CountB>{inputTextB}</CountB>
{/* 子要素に親要素の関数を渡す */}
<InputElementB inputTextFunctionB={e => inputTextFunctionB(e)}/>
</>
)
}
export default App
import React from 'react'
const InputElementA = (props) => {
console.log('InputElementAがレンダリングされた')
return (
<>
<input onChange={(e) => props.inputTextFunctionA(e.target.value)}/>
</>
)
}
export default InputElementA
import React from 'react'
const InputElementB = (props) => {
console.log('InputElementBがレンダリングされた')
return (
<>
<input onChange={(e) => props.inputTextFunctionB(e.target.value)}/>
</>
)
}
export default InputElementB
import React from 'react'
const CountA = ({children}) => {
console.log('CountAがレンダリングされた')
return (
<div>
{children.length}
</div>
)
}
export default CountA
import React from 'react'
const CountB = ({children}) => {
console.log('CountBがレンダリングされた')
return (
<div>
{children.length}
</div>
)
}
export default CountB
メモ化してレンダリング削減する場合
コンポーネントにはuseReact.memo、関数にはuseCallbackを使って不要なレンダリングを抑制します。
console.logを確認すると、不要なレンダリングが抑制されています。
import React,{useState,useCallback} from 'react'
import InputElementA from './InputElementA';
import InputElementB from './InputElementB';
import CountA from './CountA';
import CountB from './CountB';
//コンポーネントApp
const App = () => {
//親要素が持つstate変数inputTextAを定義
const [inputTextA, setInputTextA] = useState("");
//親要素が持つstate変数inputTextBを定義
const [inputTextB, setInputTextB] = useState("");
//state関数inputTextAを更新する関数inputTextFunctionAを定義
const inputTextFunctionA=useCallback((text)=> {
setInputTextA(text);
},[setInputTextA]);
//state関数inputTextBを更新する関数inputTextFunctionBを定義
const inputTextFunctionB=useCallback((text)=> {
setInputTextB(text);
},[setInputTextB]);
return(
<>
{/* inputAに入力された文字列を表示 */}
<CountA>{inputTextA}</CountA>
{/* 子要素に親要素の関数を渡す */}
<InputElementA inputTextFunctionA={useCallback((e) =>{ inputTextFunctionA(e)},[])}/>
{/* inputに入力された文字列を表示 */}
<CountB>{inputTextB}</CountB>
{/* 子要素に親要素の関数を渡す */}
<InputElementB inputTextFunctionB={useCallback((e) =>{ inputTextFunctionB(e)},[])}/>
</>
)
}
export default App
import React from 'react'
const InputElementA = React.memo((props) => {
console.log('InputElementAがレンダリングされた')
return (
<>
<input onChange={(e) => props.inputTextFunctionA(e.target.value)}/>
</>
)
});
export default InputElementA
import React from 'react'
const InputElementB = React.memo((props) => {
console.log('InputElementBがレンダリングされた')
return (
<>
<input onChange={(e) => props.inputTextFunctionB(e.target.value)}/>
</>
)
});
export default InputElementB
import React from 'react'
const CountA = React.memo(({children}) => {
console.log('CountAがレンダリングされた')
return (
<div>
{children.length}
</div>
)
});
export default CountA
import React from 'react'
const CountB = React.memo(({children}) => {
console.log('CountBがレンダリングされた')
return (
<div>
{children.length}
</div>
)
});
export default CountB
useMemo
値を保存するためのhookです。
何回やっても結果が同じ場合の値などをメモ化し、そこから値を再取得します。
不要な再計算をしないので、パフォーマンス低下を抑制できます。
依存配列に入れた変数に変化があった場合に即時に更新されます。
依存配列に空の配列を渡すと初回のみ実行されます。
const memoSample = useMemo(() => {
//処理
}, [依存配列])
useCallbackとuseMemoの違い
useCallbacl/useMemo | 特徴 | 用途 |
---|---|---|
useCallback | 関数自体をメモ化 | 子要素に関数の参照を渡す場合に使う |
useMemo | 関数の結果をメモ化 | 子要素に関数の参照を渡す場合以外に使う |
ボタンを押すとカウントアップし、カウントアップした数の2乗を表示する
useMemoを使わない場合
import React,{useState} from 'react'
//コンポーネントApp
const App = () => {
const[countA,setCountA]=useState(0);
const[countB,setCountB]=useState(0);
const increaseA=()=>{
setCountA(prevCount=>prevCount+1);
}
const increaseB=()=>{
setCountB(prevCount=>prevCount+1);
}
const doubleA=()=>{
console.log("重い処理A")
return countA*countA;
}
const doubleB=()=>{
console.log("重い処理B");
return countB*countB;
}
return(
<>
<p>カウントA:{countA}</p>
<p>カウントAの二乗:{doubleA()}</p>
<button onClick={increaseA}>+1</button>
<p>カウントB:{countB}</p>
<p>カウントBの二乗:{doubleB()}</p>
<button onClick={increaseB}>+1</button>
</>
)
}
export default App
useMemoを使う場合
useMemoを使った部分console.logが実行されなくなっていることがわかります。
import React,{useState,useMemo} from 'react'
//コンポーネントApp
const App = () => {
const[countA,setCountA]=useState(0);
const[countB,setCountB]=useState(0);
const increaseA=()=>{
setCountA(prevCount=>prevCount+1);
}
const increaseB=()=>{
setCountB(prevCount=>prevCount+1);
}
const doubleA=useMemo(()=>{
console.log("重い処理A");
return countA*countA;
},[countA])
const doubleB=useMemo(()=>{
console.log("重い処理B");
return countB*countB;
},[countB])
return(
<>
<p>カウントA:{countA}</p>
<p>カウントAの二乗:{doubleA}</p>
<button onClick={increaseA}>+1</button>
<p>カウントB:{countB}</p>
<p>カウントBの二乗:{doubleB}</p>
<button onClick={increaseB}>+1</button>
</>
)
}
export default App
その他の追加のフック
useImperativeHandle
ref使用時に親コンポーネントに渡されるインスタンス値をカスタマイズするのに使います。
useLayoutEffect
useEffectと似た効果のフックです。
フック | 説明 |
---|---|
useEffect | 「レンダリングが終了した後」に実行 |
useLayoutEffect | 「DOMに追加後ブラウザ表示する直前」に実行 |
useDebugValue
デバッグに使うフックです。
デバッグ情報をReact Dev Toolsに表示できます。