ReactHooks|今日から使えるuseEffect

この投稿は、弊社が提供するWESEEK TECH通信の一環です。
WESEEK TECH通信とは、WESEEKのエンジニアがキャッチアップした技術に関する情報を、techブログを通じて定期的に発信していくものです。

はじめに

はじめまして、システムエンジニアの渡嘉敷です。株式会社 WESEEKでインターンを経て新卒で入社、普段の業務では OSSである GROWI の開発を行っています。どうぞよろしくお願いいたします。

記念すべき初稿は、React Hooksの中でも主要で使用頻度の高い useEffectについてまとめてみました!

useEffect とは

  • レンダーの結果が画面に反映された後に実行される関数です。
  • クラスコンポーネントにおけるライフサイクルメソッドにあたります。
    componentDidMount / componentDidUpdate / componentWillUnmount
  • 関数コンポーネント内で、副作用(DOMの書き換え、変数代入、API通信などの処理など)を実行することができます

副作用に関してわかりづらい場合は、Reactにおける「副作用」とは?を一読しておくと良いでしょう

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

const UseEffectExample = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>The current count is {count}</p>
      <button onClick={() => setCount(count + 1 )}>+1</button>
      <button onClick={() => setCount(0)}>reset</button>
      <button onClick={() => setCount(count + 1 )}>+1</button>
    </div>
  )
}

export default UseEffectExample;

useStateを使ってcountのstate変数を設定しボタンをクリックする度にCount数が増えるコードを作成します。
変数countには初期値で0が指定されており、-1ボタンを押すとマイナス1、+1ボタンを押すとプラス1づつ増えていく。また、resetボタンを押すと0に戻るという仕組みのカウント機能を元に説明します。

1. 第二引数に何も渡さない場合

  useEffect(() => {
     console.log('useEffect ran');
  })

この場合、「毎回のレンダリング後」に実行されます。

2. 第二引数に空の配列を渡す場合

  useEffect(()=>{
    console.log('This should only run once')
  }, [])

第二引数に空の配列を記載した場合、「一番最初のレンダリング後」にのみ実行されます。

3. 第二引数に何らかの値(変数等)を渡す場合

  useEffect(() => {
     console.log('useEffect ran');
  },[count])

この場合、「一番最初のレンダリング後」と「第二引数に指定したstateもしくはpropsの値が変更した時」に実行されます。今回の例でいうと、最初のレンダリング後に加え、countの値に変更が生じた時に実行 されます。

第二引数の配列に具体的な値を指定してあげることで、実行されるタイミングを限定できます。これによって、不要なレンダリングが減るのでパフォーマンスの改善につながります!

クリーンアップ関数

クリーンアップ関数とはaddEventLitenerの削除、タイマーのキャンセルなどに使われます。

useEffectでクリーンアップ関数をreturnすることで、2度目以降のレンダリング時に前回の副作用を消すことができます。

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

(※ソースコードは、React 公式ドキュメントから抜粋しています)

useEffect を使用する際のTips!

クラスコンポーネントのライフサイクルでは、ライフサイクルメソッドに関係のないロジックが含まれてしまったり、関連のあるロジックが複数のメソッドに分離してしまったりすることがあります。
以下の例では、document.titleが ComponentDidMount と ComponentDidUpdateに分離してしまっています。

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

  componentDidMount() {
    document.title = You clicked ${this.state.count} times;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

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

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }
  // ...

上記で述べた問題点は、フックを使用することで解決できます。

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = You clicked ${count} times;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

ライフサイクルではなく、何を行うのかによってコードを分割 できます。
関連のあるロジックで処理を分けるので、わかりやすいだけでなく無駄なレンダリングを減らすこともできます。

(※ソースコードはReact 公式ドキュメントから抜粋しています)

おわりに

useEffectはReact Hooksの中でも特に使うシーンが多いので、しっかりと動作を理解して使用しましょう!

参考にさせていただいたサイト