hookについて①

hookとは

関数コンポーネント内でstateやライフサイクルなどのReactの機能を接続する為の関数。

useState

関数コンポーネント内でローカルなstateを使用するための関数。
引数にはstateの初期値を渡す。this.setStateと違い、オブジェクトである必要はない。

// 関数コンポーネント

import React, { useState } from 'react'; // hookを使用するにはuseStateをインポートする

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

// クラスコンポーネント
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

また[count, setCount]のように配列の分割代入構文を使用することで、useStateを呼び出して宣言した state変数に異なる名前をつける事ができる。
上記ではcountがゲッター, setCountがセッターみたいな感じ。

1つのコンポーネント内で複数のステートフックを使用できる

function ExampleWithManyStates() {
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

stateの呼び出し

クラスではthis.state.countを使用する。

  <p>You clicked {this.state.count} times</p>

関数ではcountを使用する。

<p>You clicked {count} times</p>

stateの更新

クラスではthis.setState()を呼び出すことでステートを更新する。

  <button onClick={() => this.setState({ count: this.state.count + 1 })}>
    Click me
  </button>

関数では、setCountを変数として受け取っているのでthisは不要

<button onClick={() => setCount(count + 1)}>
    Click me
  </button>

副作用(side-effect)フック

副作用とはデータの取得、購読(subscription)の設定、コンポーネント内のDOMの手動での変更などのこと。

Reactのライフサイクル(componentDidMount, componentDidUpdate, componentWillUnmount)がまとまったものだと考えられる

クリーンアップを必要としない副作用

ネットワークリクエストの送信、手動でのDOM改変、ログの記録などのコードが実行された後、すぐに忘れても良いのもの。

クラスコンポーネントの場合

componentDidMount, componentDidUpdateに記載する。 毎回のrender後にマウント直後、更新後に関係なく副作用を起こしたいが、それを可能にする関数はクラスコンポーネントには 存在しない。そのため、同じコードを2回書く必要がある

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

フックを使用した場合

render後に何らかの処理をしてほしいということをReactに伝える。 そのため、DOMの更新後に副作用を呼び出してくれる。

デフォルトでは初回のrenderと毎回の更新時に呼び出される。

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

クリーンアップを必要とする場合

何らかの外部データソースへの購読をセットアップする場合、メモリリークが発生しないようにクリーンアップが必要。

クラスコンポーネントの場合

典型的にはデータの購読をcomponentDidMountで行い、クリーンアップをcomponentWillUnmounで行う。

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}

ライフルメソッドを使用した場合、概念上は同一の副作用に関連していても、それらを分割して書かないといけない。

フックの場合

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // クリーンアップのための関数を返す!!
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

useEffect()はクリーンアップするための関数を返すことができる。これによりライフルメソッドと違い、同一の関数内で 宣言することができる。

参考

フック早わかり – React