17
15

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 基礎

Last updated at Posted at 2021-11-07

目次

  1. react-hooksについて
  2. useState
  3. useEffect
  4. useRef
  5. React.memo / useCallback / useMemo
  6. useReducer
  7. Context / useContext
  8. カスタムフック

1. react-hooksについて

  • Reactのバージョン16.8.0で追加された機能
  • 関数コンポーネントで利用できる関数のこと
  • クラスコンポーネントでしかできなかったこと(state管理)が、関数コンポーネントでもできるようになる
  • クラスコンポーネントよりもコード量を減らせる
  • ロジックを分離できるので、ロジックの再利用やテストがしやすい

2. useState

  • state, state更新関数を返すフック
  • state管理とは、stateの保持とstateの更新をすること
import React, { useState } from "react";

//構文フォーマット
const [state, state更新関数] = useState(stateの初期値);

//初期値が0の「count」というstateと
//「setCount」というcountを更新する関数を定義
const [count, setCount] = useState(0);

const countMinus = (currentCount) => currentCount - 1;

//countが-1される
setCount(countMinus);

3. useEffect

  • コンポーネントのレンダー後かアンマウント後に、副作用(何らかの処理)を実行させる
import React, { useState, useEffect } from "react";

//構文フォーマット
useEffect(副作用, 依存配列);

//コンポーネントがレンダーされるたびに副作用を実行させる
//第1引数の副作用だけを渡しても動作する
useEffect(() => {
  console.log('completed render');
})

//副作用に依存する値が更新した時だけ、副作用を実行させる
useEffect(() => {
  console.log(message);
}, [message]);

//副作用を1度だけ実行させる
//第2引数に空の配列を指定する
useEffect(() => {
  console.log('completed render');
}, []);

//副作用内で関数を返すと、コンポーネントがアンマウントor副作用が再実行した時に実行される
//この関数のことを「クリーンアップ関数」と言う
useEffect(() => {
  return () => {
    console.log('clean up');
  }
});

4. useRef

  • refオブジェクト(React.createRefの戻り値)を返すフック
  • refオブジェクトを利用することで、DOMの参照やコンポーネント内で値を保持できる
  • useStateとは異なり、useRefで生成した値を更新してもコンポーネントは再レンダーされない(値を更新しても、再レンダーしたくない時などに使用する)
import React, { useState, useEffect, useRef } from "react";

//構文フォーマット
const refオブジェクト = useRef(初期値);

//useRefの引数に渡した値が、refオブジェクトのcurrent
プロパティの値になる
const count = useRef(0);
console.log(count.current) // -> 0

//更新
count.current = const.current + 1;

DOMの参照

useRefでDOMを参照したい場合、作成したrefオブジェクトをHTML要素のref属性に指定する必要がある

//以下により、inputEl.currentでDOMを参照できる
const inputEl = useRef(null);
<input ref={inputEl} type="text" />

5. React.memo / useCallback / useMemo

不要な再計算やコンポーネント再レンダーを抑えるための手段が以下の3つ

React.memo

  • コンポーネント(コンポーネントのレンダー結果)をメモ化するReactのAPI
  • メモ化とは、計算結果を保持し、それを再利用する手法のこと
import React, { useState } from "react";

//構文フォーマット
React.memo(関数コンポーネント)

//Helloというコンポーネントをメモ化する
//これにより、前回のpropsと比較して、等価であれば再レンダーせずに、メモ化したコンポーネントを再利用する
const Hello = React.memo(({ name } => 
{
  return <h1>Hello {name}</h1>
});

useCallback

  • メモ化されたコールバック関数を返すフック
  • React.memoでメモ化したコンポーネントにuseCallbackでメモ化したコールバック関数をpropsとして渡すことで、コンポーネントの不要な再レンダーをスキップできる
import React, { useState, useCallback } from 'react';

//構文フォーマット
useCallback(コールバック関数, 依存配列);

//countという変数を出力する関数をメモ化
const callback = useCallback(() => 
console.log(count), [count]);

//依存している値がなければ、依存配列は空でOK
const callback = useCallback(() => console.log('hello'), []);

注意点
useCallbackはReact.memoと併用するものなので、次のような使い方をしてもコンポーネントの不要な再レンダーをスキップできない

  • React.memoでメモ化をしていないコンポーネントにuseCallbackでメモ化をしたコールバック関数を渡す
  • useCallbackでメモ化したコールバック関数を、それを生成したコンポーネント自身で利用する

useMemo

  • メモ化された値を返すフック
  • コンポーネントの再レンダー時に値を再利用できるため、値の不要な再計算をスキップできる
import React, { useState } from 'react';

//構文フォーマット
useMemo(() => 値を計算するロジック, 依存配列);

//countという変数の値を2倍にした値をメモ化する
const result = useMemo(() => count * 2, [count]);

6. useReducer

  • state, dispatch(actionを送信する関数)を返すフック
  • このフックを利用すれば、コンポーネント内でstate管理できる

actionとは?
アクション(何が起きたのか)とそれに付随する情報を持つオブジェクトのこと

//actionの例
//ADD_TODOというアクションタイプと、それに付随する情報text:'Learning React'を持つ
const action = {
  type: 'ADD_TODO',
  text: 'Learning React'
}
import React, { useReducer } from 'react';

//構文フォーマット
//state: stateのこと
//dispatch: actionを送信する関数
//reducer: 現在のstateとactionを受け取り、actionに応じて更新したstateを返す関数
const [state, dispatch] = useReducer(reducer, stateの初期値);

//reducerの関数
function reducer(state, action){
  if(action.type === 'INCREMENT'){
    return { count: state.count + 1 };
  } else {
    return state
  }
}

export default function App() {

  //stateの初期値{count: 0}で定義
  const [state, dipatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      <p>count: {state.count}</p>
      <button onClick={() => dipatch({ type: 'INCREMENT' })}>プラス</button>
    </>
  )
}

useStateとの使い分け

複雑なstateを扱う場合は、useReducerを使用したほうが良い
(別のstateに依存している、stateの更新のロジックが複雑、など...)

7. Context / useContext

useContextは、ContextというReactの仕組みを利用するために必要なフック

Context

Reactにおける「Context」は、以下のいずれかを表す

  1. Propsを利用せずに様々な階層のコンポーネントに値を共有するReactの仕組み
  2. Propsを利用せずに様々な階層のコンポーネントに値を共有するReactのAPI
  3. Contextオブジェクトのこと
  4. Contextオブジェクトの値のこと

Contextの利用には以下の3つが必要

  • Contextオブジェクト
  • Provider
  • Consumer

Contextオブジェクト

  • React.createContextというReactのAPIの戻り値
  • このContextオブジェクトが保持している値をProviderで共有できる
//構文フォーマット
const MyContext = React.createContext();

Provider

  • Contextオブジェクトが保持しているコンポーネントのこと(Providerコンポーネントとも言う)
  • 共有した値はConsumerを定義することで、受け取れる
import React, { createContext } from 'react';

//Contextオブジェクト
const MyContext = createContext();

export default function App(){
  const name='soarflat';
  return(
    <>
      {/*Provider。valueプロパティの値を共有する。*/}
      <MyContext.Provider value={name}>
      {/*MyContext.Providerにラップされているので、valueプロパティの値を取得できる。*/}
        <Child/>
      </MyContext.Provider>

      {/*MyContext.Providerにラップされていないので、valueプロパティの値を取得できない。*/}
      <Brother/>
    </>
  );
}

Consumer

  • Contextオブジェクトから値を取得しているコンポーネントのこと(Consumerコンポーネントとも言う)
import React, { createContext, useContext } from 'react';

//Contextオブジェクト
const MyContext = createContext();

//MyContextから値を取得しているのでConsumerである
function Child1(){
  //<MyContext.Provider value={name}>のnameは'React'なので、textの値は'React'になる。
  const text = useContext(MyContext);
  return <h1>{text}</h1>;
}

//MyContextから値を取得していないのでConsumerではない
function Child2(){
  return <h2>NotConsumer</h2>;
}

export default function App() {
  const name = 'React';

  return (
    {/*Provider。valueプロパティの値をConsumerに共有する。*/}
    <MyContext.Provider value={name}>
      <Child1/>
      <Child2/>
    </MyContext.Provider>
  );
}

useContext

  • Contextオブジェクトから値を取得するフック
import React, { useState, useContext, createContext } from "react";

//構文フォーマット
const Contextオブジェクトの値 = useContext(Contextオブジェクト)

//例
//Contextオブジェクト
const MyContext = createContext();

...

const context = useContext(MyContext);
return (
  <>
    <p>{context.name}</p>
    <button onClick={context.handleClick}>increment</button>
  </>
)

Contextの使い所

  • 複数のコンポーネントで共通利用したいデータがあるが、コンポーネントの数が多かったり、階層が深いので、Propsで渡すのが困難
  • Prop drilling問題を解消したいが、Context以外の手段でProp drilling問題を解消するのが困難
    (Prop drilling問題とは、コンポーネント間でのprop受け渡しの回送が深くなってしまう問題のこと)

ただし、Contextには以下のようなデメリットもある

  • コンポーネントがContextに依存するため、コンポーネントの再利用性が低下する
  • Contextオブジェクトの値はグローバルなstateなので、何も考えずに利用すると、値がどこで利用されていたり更新されているかがわかりづらくなる

具体例

  • 認証情報 : ログイン情報など
  • テーマ : 選択しているテーマに応じて、コンポーネントに適用するスタイルを制御するために利用する
  • 言語 : 表示言語の切り替え

8. カスタムフック

  • 自作のフックのこと(コンポーネントから切り出したロジックを定義した関数)
  • カスタムフックの名前は必ずuseから始まる必要がある
import React, { useState } from 'react';

//例
//カウンターのstateとstate更新ロジックを持つカスタムフック
//カスタムフックの名前は必ずuseから始まる必要がある
function useCounter(initialCount){
  const [count,setCount] = useState(initialCount);
  const increment = () => { setCount(count+1); };
  const decrement = () => { setCount(count1); };
  return { count, increment, decrement };
}

//カスタムフックを使用
function App() {
  const { count,increment,decrement } = useCounter(0);
  return(
    <div>
      <p>count:{count}</p>
      <button onClick={decrement}>-</button>
      <button onClick={increment}>+</button>
    </div>
  );
}

export default App;
17
15
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
17
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?