React Hooks(追加のHook)

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

- useReducer
- useRef
- useCallback
- useMemo


[React Hooks(基本のHook)](https://qiita.com/takeshisakuma/items/39b5f2c0fe4d4c6ad378)






## 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](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)に表示できます。