🌐

Re:(act)ゼロからはじめるHooks API

2020/12/04に公開

はじめに

こういう方はいませんか?

  • Reactは書いたことあるけど結局Hooksってなんなの?
  • 雰囲気でuseState, useEffectを使っている
  • なんかわからんけど最近は関数コンポーネントとHooksが流行りなんだよね?

他にも

  • そもそもHooksってなんなの?
  • Reduxは関係ある?
  • なんのためにあるの?
  • 今まで通りClass Componentでよくない?
  • 使わないとダメ?

本記事は上記のような人が「Hooks完全に理解した」というためのものです。
(「完全に理解した」というのは完全に理解することではない)

Reactの公式ドキュメントを読もう

Reactの公式ドキュメントは大変読みやすく、本記事の内容の多くはそこに書かれています。

Hooksとは

ざっくりいうと「関数コンポーネントでクラスコンポーネントのようないろいろな機能を使うためのReactのAPI」です。
わかるようで「なにがうれしいのか」「なにができるのか」がよくわからないですね。
掘り下げていきましょう。

関数コンポーネントとクラスコンポーネント

「関数コンポーネント」と「クラスコンポーネント」という言葉が出てきました。
これらについて考えてみましょう。

以下はそれぞれの簡単な実装例です。ざっと眺めて何か感じることはないでしょうか?

// 関数コンポーネント
const WelcomeButton: React.FC = (props) => {
  const handleClick = () => {
    console.log(`Hello, ${props.name}`);
  };
  return <button onClick={handleClick}>Hi</button>;
};

// クラスコンポーネント
class WelcomeButton extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log(`Hello, ${this.props.name}`);
  }
  render() {
    return <button onClick={this.handleClick}>Hi</button>;
  }
}

関数コンポーネントのほうがスッキリしていませんか?
そうであれば関数コンポーネントで書きたいと思うのは自然な動機付けではないでしょうか。

関数コンポーネントの弊害

いざ関数コンポーネントで書き始めると起こることがあります。

  • 書いてるうちにStateを持たせたくなった…
  • 特定のライフサイクルで処理をしたい…

こういう場合、クラスコンポーネントに書き直さないといけないのでしょうか。
答えはイエスです。
では再度先程の例を見て、書き換えに必要な手順を上げてみましょう

// 関数コンポーネント
const WelcomeButton: React.FC = (props) => {
  const handleClick = () => {
    console.log(`Hello, ${props.name}`);
  };
  return <button onClick={handleClick}>Hi</button>;
};
// クラスコンポーネント
class WelcomeButton extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log(`Hello, ${this.props.name}`);
  }
  render() {
    return <button onClick={this.handleClick}>Hi</button>;
  }
}

クラスコンポーネントに直すために必要な工程は以下です。

  • classキーワードの追加
  • constructorの追加
  • renderの追加

しかし、本来やりたかったのは

  • Stateを使いたい

これだけでしたね。
本質的でない変更が多いのは嬉しくありません。

ここで思い出すのは...

Hooksとは
関数コンポーネントでクラスコンポーネントのようないろいろな機能を使うためのReactのAPI

→ そうだHooksを使おう

つまり、Hooksでうれしいことは

関数コンポーネントのまま必要な機能だけを絞って追加できること

Hooksの種類

Hooksがなにかはわかりました。
Hooksにはどんなものがあるのか見ていきましょう。

Hooksは大きく分けて次の2種類あります。

  • React公式が公開している10種のAPI
  • カスタムhookとよばれる一定のルールに基づいたユーザー定義関数

ここで、後者はオフィシャルじゃないので一旦忘れるとしましょう。

つまり

10個わかればHooksを完全に理解できる

ということです。
しかも、10個の中にほぼ使わないものもあるので、覚えるべきことは更に減ります。

React公式が公開しているAPI

先述したAPI10種ですが、公式ドキュメントではこれらをさらに2つに分けています。
それを踏まえて10種を並べると次のとおりです。

  • 基本のフック: 3種
    • useState
    • useEffect
    • userContext
  • 追加のフック: 7種
    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue

Hooksを使ってみよう

Hooksを使う際の2つのルール

Hooks APIを使う場合に、守るべき2つのルールがあります。

  • Reactの関数コンポーネントまたはカスタムhookから呼び出す
    • →クラスコンポーネントでは使えない
  • 関数のトップレベルに置く

これらはHooksに期待通りの動作をさせるために必要なものです。
ESLintのプラグインとしてeslint-plugin-react-hooksという名前で公開されてるので、Hooksを用いるプロジェクトでは強制しましょう。

useStateの使い方

ここからはそれぞれの説明に移ります。
useStateは

  • 状態を扱うhook
  • 誤解を恐れずに言うとクラスコンポーネントのState

useStateに初期値を渡すと、state変数と更新関数が返ってくる」というのが基本的な使い方です。

const Counter:FC = () => {
  const [count, setCount] = useState(0); // 初期値0を渡している
  return (
   <div>
     <button onClick={()=>setCount(count+1)} > // 更新関数setCountでcountを更新
       count up
     </button> 
     <p>{count}</p> // 初期値0のstate変数
  </div>);
}

上記は状態が1つのケースでしたが、状態が複数ある場合も考えてみましょう。

クラスの場合はstateをオブジェクトとして宣言して、setStateで更新をマージする形になります。

constructor(props) {
  super(props);
  this.state = {
    name: "taro",
    age: 10
  }
}

this.setState({
  age: 30
})

ではHooksの場合はどうでしょうか?次のように書きます

  const [name, setName] = useState("taro");
  const [age, setAge] = useState(20);

  setAge(30);

useStateを複数用いて、それぞれのstate変数・更新関数を定義します。
ここで起きているのは、関心を分離しているということです。

useState何が嬉しいの

useStateのメリットを整理すると次のようになります。

  • 関数コンポーネントに状態をもたせることができる
  • クラスコンポーネントに書き直さなくて良い
  • 記述がシンプル
  • 複数のuseState用いて関心を分割することができる

useEffectの使い方

次にuseEffectについて考えます。
useEffectは

  • 副作用を扱うhook
    です。

公式ドキュメントから引用すると、

Reactのライフサイクルに馴染みがある場合は、useEffectフックをcomponentDidMountとcomponentDidUpdateとcomponentWillUnmountがまとまったものだと考えることができます。

とあります。

useEffectの使い方としては以下です。

  • useEffectの第1引数に副作用をもつ関数を渡す
  • 副作用関数の戻り値に関数を設定することでcleanup処理を行える
  • useEffectの第2引数に渡す配列が変更されない場合、useEffectは処理をスキップする

例を見てみましょう。

const User:FC = ({userId}) => {
  const [userName, setUserName] = useState();
  
  useEffect(async() => { // 副作用
    const result = await axios(`https://example.com/username/${userId}`);
    setUser(result.data);
  }, [userId]);
  
  return (
    <div>
     name: {userName}
    </div>
  );
}

useEffectの第1引数にfetch処理を書いています。[1]
また、第2引数にuseIdを指定することで、当該値が変更された場合にfetch処理を行っています。

useEffect何が嬉しいの

useEffectのメリットは以下です。

  • 関数コンポーネントからfetchやタイマー処理などをの副作用を持つ処理を行うことができる
  • ライフライクル単位ではなく、責務を単位にロジックを分割することができる

useContextの使い方

useContextは

  • Contextを扱うhook

です。

コンテクストってなんだっけ?

公式ドキュメントから引用すると

コンテクストは各階層で手動でプロパティを下に渡すことなく、コンポーネントツリー内でデータを渡す方法を提供します。

Contextはprops drillingを避けるためのReactのAPIということがわかりました。

useContextの使用例を見てみましょう。

const ThemeContext = React.createContext(themes.light); // Contextの生成

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}> // Providerの子孫コンポーネントではContextが伝播している
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext); // useContextにContextを渡すと値が取得できる
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

useContext何が嬉しいの

useContextのメリットをまとめます。

  • コンポーネント間でグローバルな状態を共有することができる
  • 認証情報、UIテーマなどが主な用例

時々使う追加のフックたち

追加のHooksについてはさらっと流します。

  • useReducer
    • reducerを定義してdispatchする局所的なreduxっぽいことができる
  • useCallback
    • 関数のメモ化によるパフォーマンスの最適化
  • useMemo
    • 処理結果のメモ化によるパフォーマンスの最適化
  • useRef
    • 子コンポーネントに命令したりDOMにアクセスしたり

ほぼor全く使わない追加のフック

こちらは「こういうのがあるんだな」程度で良いと思っています。

  • useImperativeHandle
    • 親コンポーネントから制御を受けるためのhook
  • useLayoutEffect
    • 同期的なuseEffect
  • useDebugValue
    • カスタムhookのデバッグに用いる

カスタムhook

カスタムHookは「Hooksの考え方といってのルールに従ったユーザー定義関数」です。

  • useXXXという命名をする
  • Hooksを使用することができる
  • 各種ライブラリもカスタムhookという形でHooks対応している
    • react-reduxuseSelectorなど

カスタムHook何が嬉しいの?

カスタムHookのメリットは以下です。

  • コンポーネントからロジックの分離
  • ロジックの再利用性

まとめ

完全に理解できましたか?

はじめに挙げた疑問に回答する形でおさらいしてみましょう。

  • そもそもHooksってなんなの?
    • 関数コンポーネントを便利に使うAPI
  • Reduxは関係ある?
    • 直接はないが、react-reduxはhooks対応している(useDispatchなど)
  • なんのためにあるの?
    • 関数コンポーネントを便利に使いたい
    • ロジックを分離したい
  • 今まで通りClass Componentでよくない?
    • そちらが書きやすければどうぞ!
  • 使わないとダメ?
    • ダメじゃないよ!でも便利だよ
脚注
  1. async関数を設定するとPromiseを返すためcleanup処理を設定できないことに留意 ↩︎

Discussion