[React] Hooks


Hooks


HookはReact 16.8バージョンから新しく追加されたステータスとライフサイクル管理APIです.Hookでは、クラスエレメントを使用せずに関数エレメント内にステータスを保存し、ライフサイクル管理を行うことができます.

useState


useStateはステータス管理用のhookです.

Create initial state

const [<상태 저장 변수>, <상태 갱신 함수>] = useState(<상태 초기 값>);
初期状態生成時には、状態格納変数の初期値をarray、number、booleanなど複数のタイプと値に指定できます.また、userStateは、パラメータとして非値関数を使用できます.この場合、最初のレンダリング時にのみ実行されます.

Update State

const [count, setCount] = useState(0)

setCount(prevCount => prevCount + 1)
初期状態生成時には、宣言された状態更新関数で状態格納変数の値を更新できます.

Example


useState hookとステータスストレージ変数countを使用したカウンタの例
function Counter() {
  const [count, setCount] = useState(0) // creating initial state

  function changeCount(amount) {
    setCount(prevCount => prevCount + amount)
  }

  function resetCount() {
    setCount(0)
  }

  return (
    <>
      <span>{count}</span>
      <button onClick={() => changeCount(1)}>+</button>
      <button onClick={() => changeCount(-1)}>-</button>
      <button onClick={() => resetCount()}>Reset</button>
    </>
  )
}

useEffect


useEffect(function, deps)
userEffectは、クラス要素のライフサイクルメソッドに代わって、要素がレンダリング時またはレンダリング後に特定の操作を実行できるようにするhookです.

Creating Your First Side Effect


ネットワークリクエスト、データインポート、サブスクリプション設定、構成部品の手動変更に影響するDOMなど、現在の関数を超える範囲を副作用と呼びます.useEffectは、これらの副作用を処理するために使用される.
userEffectを使用する最も基本的な方法は、単一の関数(single function)を渡すことです.
useEffect(() => {
  console.log('This is a side effect')
})
この場合、この操作は、エレメントの最初のマウント、propsの変更、ステータスの変更など、レンダリングのたびに実行されます.ただし、取り付け段階でのみ副作用が必要な場合や、一部の支柱や状態が変更される場合は望ましくありません.
2番目のパラメータである依存配列(Dependency Array)を使用すると、最後のレンダーの依存性が前のレンダーの依存性と比較して変化すると、副作用が発生します.インストールフェーズでのみ実行する場合は、空の配列を2番目のパラメータとして渡すことができます.
useEffect(() => {
  console.log('Only run on mount')
}, [])


useEffect(() => {
  console.log('Only run on url change')
}, [url])

Cleaning Up Side Effects


アンインストール前、更新前に副作用が必要な場合はcleanup関数を返さなければなりません.
useEffect(() => {
  console.log('This is my side effect')

  return () => {
    console.log('This is my clean up')
  }
})
インストール後、上記のuseEffectを含む構成部品を2回再レンダリングしてアンインストールすると、コンソール出力は次のようになります.
// MOUNTED
// This is my side effect

// RE-RENDER 1:
// This is my clean up
// This is my side effect

// RE-RENDER 2:
// This is my clean up
// This is my side effect

// UN-MOUNT:
// This is my clean up

useMemo & useCallback


useMemoとuseCallback hookを理解するにはmemoizationを理解する必要があります.

What is Memoization?


Memoizationは本質的にキャッシュと同じ意味です.同じ計算を繰り返す関数を実行する場合、値を再計算するよりも、キャッシュの値を使用して処理速度を迅速に向上させます.
const cache = {}

function slow(a) {
  if (cache[a]) return cache[a]
  
  const result = /* Complex logic */
  cache[a] = result
  return result
}
すべてのエレメントロジックがレンダリングされるたびに再計算され、ロジックの計算速度が遅い場合に急激に低下する問題を解決するために、hookはuseMemoとuseCallbackです.

useMemo


useMemoはuseEffectと同様に動作し、2番目のパラメータ依存性が変化した場合にのみmemorization値を再計算します.すなわち、useMemoはコメントされた値を返します.
const result = useMemo(() => {
  return slowFunction(a)
}, [a])
userMemoに渡される関数は、レンダリング中に実行されます.2番目のパラメータがない場合は、レンダーするたびに新しい値が計算されます.

useCallback


useCallbackは依存配列キャッシュ結果に基づいているため、useMemoと同様に動作します.ただし、useCallbackはキャッシュの値ではなく、キャッシュの関数に使用されます.すなわち、useCallbackは、コメントされたコールバックを返します.
const handleReset = useCallback(() => {
  return doSomething(a, b)
}, [a, b])
構文はusemoと同じですが、依存性が変化するたびに、usemoは伝達された関数を呼び出し、呼び出しの値を返します.逆に、依存性が変化するたびにuseCallbackは関数を呼び出すのではなく、関数の新しいバージョンを返します.
useCallback(() => {
  return a + b
}, [a, b])

useMemo(() => {
  return () => a + b
}, [a, b])
上のコードでは、useCallbackとuseMemoは同じ値を返しますが、useCallbackは伝達された関数を返し、useMemoは伝達された関数の結果を返します.
また、useMemoとuseCallbackは、参照を等しく保つために使用されます.
function Parent() {
  const [items, setItems] = useState([])
  const handleLoad = (res) => setItems(res)

  return <Child onLoad={handleLoad} />
}

function Child({ onLoad }) {
  useEffect(() => {
    callApi(onLoad)
  }, [onLoad])
}
上記の例では、handleLoad要素がレンダリングされるたびに、Parent関数が再作成されます.これは、onLoad関数が各レンダリングにおいて異なる参照同等性を有するため、Child要素の使用効果が再実行されることを意味する.
function Parent() {
  const [items, setItems] = useState([])
  const handleLoad = useCallback((res) => setItems(res), [])

  return <Child onLoad={handleLoad} />
}

function Child({ onLoad }) {
  useEffect(() => {
    callApi(onLoad)
  }, [onLoad])
}
handleLoadでuseCallbackを使用すると、handleLoad関数は変更されず、Child要素がレンダリングされるたびに呼び出されません.

useRef


userefは、DOM要素に直接アクセスおよび操作するためのhookです.また、レンダリング間でデータを保持することもできます.
userefを使用するには、まずrefを初期化する必要があります.
useRef(initialValue)
userefはcurrentという単一のPropertyというオブジェクトを返し、初期値は現在のPropertyに割り当てられます.
const myRef = useRef(0);

console.log(myRef);
// { current: 0 }
宣言後にuserefを再レンダリングしても、同じ参照は続行されます.また、参照が変更されても、構成部品は再レンダリングされません.すなわち、refはレンダリング間の持続値を格納するオブジェクトである.
function State() {
  const [rerenderCount, setRerenderCount] = useState(0);

  useEffect(() => {
    setRerenderCount(prevCount => prevCount + 1);
  });

  return <div>{rerenderCount}</div>;
}
function Ref() {
  const rerenderCount = useRef(0);

  useEffect(() => {
    rerenderCount.current = rerenderCount.current + 1;
  });

  return <div>{rerenderCount.current}</div>;
}
State構成部品では、ステータスが更新されると、構成部品は再レンダリングされますが、Ref構成部品のrefは値が変更されたときに再レンダリングされません.

How to use Refs


refは、通常、DOM要素を参照するために使用される.たとえば、ボタンをクリックするたびにinput要素にカーソルを移動する場合は、refを使用して次のコードを記述します.
function Component() {
  const inputRef = useRef(null)

  const focusInput = () => {
    inputRef.current.focus()
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </>
  )
}

Using Refs beyond the DOM


refは、レンダリング間の記憶領域として使用することができる.
unction Component() {
  const [name, setName] = useState('Kyle')
  const previousName = useRef(null)

  useEffect(() => {
    previousName.current = name
  }, [name])

  return (
    <>
      <input value={name} onChange={e => setName(e.target.value)} />
      <div>{previousName.current} => {name}</div>
    </>
  )
}
上記の例は、ステータス変数nameが変更されるたびにrefが更新され、ステータス変数nameの以前の値を保存するコードである.

useContext


What is Context API?


反応器ではstateはデータとpropsを格納し,素子間でデータを伝達するために用いられる.しかしながら、多くの素子がネスト構造で状態を伝達するには、複数回の複雑なステップが必要であるため、メンテナンスが困難である.
Context APIを使用すると、コンポーネントを介してデータを渡すことなく、Context内のすべてのネストされたコンポーネントの使用可能なデータを指定できます.このデータは半グローバル状態であり,Context内部のどこでも利用できる.
const ThemeContext = React.createContext()

function App() {
  const [theme, setTheme] = useState('dark')

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <ChildComponent />
    </ThemeContext.Provider>
  )
}
function ChildComponent() {
  return <GrandChildComponent />
}
class GrandChildComponent {
  render() {
    return (
      <ThemeContext.Consumer>
        {({ theme, setTheme }) => {
          return (
            <>
              <div>The theme is {theme}</div>
              <button onClick={() => setTheme('light')}>
                Change To Light Theme
              </button>
            </>
          )
        }}
      </ThemeContext.Consumer>
    )
  }
}
上記のコードでは、React.createContextを用いて2つの部分の変数が生成される.
第1の部分は、すべてのオーバーラップ要素に値を提供するプロバイダである.上記のコードは、themeおよびsetThemeの単一のオブジェクトです.
第2部は消費者です.Contextの値にconsumerからアクセスするには、コードを記述する必要があります.構成部品のサブ要素をカプセル化する関数のパラメータとしてContext値を指定します.
しかし、第2部では、Contextの値を取得するために、関数を含むコンポーネントにJSXをカプセル化することで、コードの重複や複雑なレイヤが増加するという問題がある.

useContext


useContextを使用してContextをuseContext hookに渡し、consumerでJSXコードを記述することなく関数コンポーネントでContextを使用します.
function GrandChildComponent() {
  const { theme, setTheme } = useContext(ThemeContext)

  return (
    <>
      <div>The theme is {theme}</div>
      <button onClick={() => setTheme('light')}>
        Change To Light Theme
      </button>
    </>
  )
}
useContextを使用すると、既存のconsumerセクションの複雑な重複を解消できます.Contextは、Contextの通常の関数を呼び出すように、Context内部の値を提供します.useContextを設定するproviderは、既存のContext APIと全く同じです.
Reference
  • Hooks API Reference
  • Everything You Need To Know About useState
  • Everything You Need To Know About useEffect
  • How To Use Memoization To Drastically Increase React Performance
  • How To Use Refs In React With Hooks
  • How To Use Context In React With Hooks