0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

React Hooks(追加のHook)

Last updated at Posted at 2020-12-07

追加のフック

React組み込みのフックのうち、追加のフックを試していきます。

  • useReducer
  • useRef
  • useCallback
  • useMemo

React Hooks(基本のHook)

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プロパティと値のプロパティで構成します。

jsx
{
  type:increment,
  payload:0
}
jsx
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が単数の場合

src/Counter.js
//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
src/App.jsx
// 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が扱うデータを複数にして、増減の値も変えてみます。

src/Counter.2jsx
//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
App.jsx
// 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で参照できます。

jsx
const refObject = useRef(initialValue)
 
//例
const number = useRef(100); // {current: 100}
console.log(number.current); // 100

DOMを参照する

js
const inputElement = useRef(null)
 
//例
<input ref={inputElement} type="text" />
console.log(inputElement); // current: null
js
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

パフォーマンス低下抑制のためのフックです。
メモ化したコールバック関数を返します。

メモ化

初回の処理を記録しておきます。
依存配列の要素のいずれかが変化した場合のみ、メモ化した値を再計算することで、不要なレンダリングを抑制します。

jsx
const callback = useCallback(関数,[依存配列])

Reactコンポーネントの再レンダリング条件

条件を1つでも満たした場合には再レンダリングされます。

条件 内容
propsの更新 親の再レンダリングによる無意味なpropsの更新の場合、useCallbackで抑制
stateの更新 レンダリングは必要なので、メモを破棄してレンダリングする
親コンポーネントが再レンダリング時 不要な場合React.memoで抑制

入力された文字をカウントする

コンポーネントがレンダリングされるとconsole.logで確認できるようにします。

メモ化していない場合

src\App.js
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
src\InputElementA.js
import React from 'react'
const InputElementA = (props) => {
 
  console.log('InputElementAがレンダリングされた')
 
  return (
 
    <>
      <input onChange={(e) => props.inputTextFunctionA(e.target.value)}/>
    </>
 
  )
 
}
 
export default InputElementA
src\InputElementB.js
import React from 'react'
 
const InputElementB = (props) => {
 
  console.log('InputElementBがレンダリングされた')
 
  return (
 
    <>
      <input onChange={(e) => props.inputTextFunctionB(e.target.value)}/>
    </>
 
  )
 
}
 
export default InputElementB
src\CountA.js
import React from 'react'
 
const CountA = ({children}) => {
 
  console.log('CountAがレンダリングされた')
 
 
  return (
    <div>
      {children.length}
    </div>
  )
}
 
export default CountA
src\CountB.js
import React from 'react'
 
const CountB = ({children}) => {
 
  console.log('CountBがレンダリングされた')
 
  return (
    <div>
      {children.length}
    </div>
  )
}
 
export default CountB

メモ化してレンダリング削減する場合

コンポーネントにはuseReact.memo、関数にはuseCallbackを使って不要なレンダリングを抑制します。
console.logを確認すると、不要なレンダリングが抑制されています。

src\App.js
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
src\InputElementA.js
import React from 'react'
 
const InputElementA = React.memo((props) => {
 
  console.log('InputElementAがレンダリングされた')
 
  return (
 
    <>
      <input onChange={(e) => props.inputTextFunctionA(e.target.value)}/>
    </>
 
  )
 
});
 
export default InputElementA
src\InputElementB.js
import React from 'react'
 
const InputElementB = React.memo((props) => {
 
  console.log('InputElementBがレンダリングされた')
 
  return (
 
    <>
      <input onChange={(e) => props.inputTextFunctionB(e.target.value)}/>
    </>
 
  )
 
});
 
export default InputElementB
src\CountA.js
import React from 'react'
 
const CountA = React.memo(({children}) => {
 
  console.log('CountAがレンダリングされた')
 
  return (
 
    <div>
      {children.length}
    </div>
  )

});
 
export default CountA
src\CountB.js
import React from 'react'
 
const CountB = React.memo(({children}) => {
 
  console.log('CountBがレンダリングされた')
 
  return (
 
    <div>
      {children.length}
    </div>
  )
});

export default CountB

useMemo

値を保存するためのhookです。
何回やっても結果が同じ場合の値などをメモ化し、そこから値を再取得します。
不要な再計算をしないので、パフォーマンス低下を抑制できます。

依存配列に入れた変数に変化があった場合に即時に更新されます。
依存配列に空の配列を渡すと初回のみ実行されます。

jsx
const memoSample = useMemo(() => {
  //処理
}, [依存配列])

useCallbackとuseMemoの違い

useCallbacl/useMemo 特徴 用途
useCallback 関数自体をメモ化 子要素に関数の参照を渡す場合に使う
useMemo 関数の結果をメモ化 子要素に関数の参照を渡す場合以外に使う

ボタンを押すとカウントアップし、カウントアップした数の2乗を表示する

useMemoを使わない場合

Ajs
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が実行されなくなっていることがわかります。

js
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に表示できます。

0
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?