LoginSignup
0
1

[React] Effectについて理解する

Posted at

Effectとは?

React Hooksの一種で副作用を実行するための処理

  • APIからのデータ取得
  • DOMの更新

構文

副作用は useEffect()によって定義される。

第一引数

副作用を実行するコールバック関数(引数なし)を指定する。

useEffect(() => {
  console.log('レンダリングごとに実行');
});

クリーンアップ関数

第一引数の関数はクリーンアップ関数を返すことが可能。
クリーンアップ関数は 「コンポーネントのアンマウント時に実行」 される

useEffect(() => {
  console.log('レンダリングごとに実行');

  return () => {
    console.log('アンマウント時に実行');
  };
});

第二引数

第二引数には 「依存配列」 を指定することができる。

依存配列

副作用関数が依存する値を配列形式で指定する。
「配列内の値が変更されるたびに、指定した副作用が再実行」 される。


依存配列を省略した場合

依存配列を省略すると、第一引数の関数は 「コンポーネントがレンダリングされるたびに実行」 される。

useEffect(() => {
  console.log('レンダリングごとに実行');
});

依存配列に[] を指定した場合

依存配列に空の配列 [] を指定すると、副作用は 「コンポーネントのマウント時に一度だけ実行」 され、アンマウント時にクリーンアップ関数が実行されます。

useEffect(() => {
  console.log('マウント時に一度だけ実行');

  return () => {
    console.log('アンマウント時に実行');
  };
}, []);

依存配列に値を指定した場合

userId の値が変わるたびに fetchUserProfile 関数が呼ばれ、新しいユーザープロファイルを取得する

useEffect(() => {
  fetchUserProfile(userId).then(data => {
    setUserProfile(data);
  });
}, [userId]);

依存配列のWarning

以下のコードはtimeLeftmaxCountを副作用の中で使用しているが、これらが依存配列に含まれていないためにwarningが発生する。

useEffect(() => { 
  if (count === 0) setCount(maxCount); 
});

発生理由

この副作用は countの値を確認、 → maxCountを利用して値を更新しているが、
count 及び、 maxCountの値に変更がない場合、 副作用の実行に意味がないため

正しい実装

useEffect(() => { 
  if (timeLeft === 0) setTimeLeft(maxCount); 
}, [timeLeft, maxCount]);

このようにすることで関連する副作用が適切に実行される。

useEffectは複数定義可能

useEffect はコンポーネント内で複数回定義することが可能。
これにより、異なる副作用を分けて管理できる。

useEffect(() => {
  console.log('レンダリングごとに実行');
}, []);

useEffect(() => {
  console.log('レンダリング');
});

サンプルコード

ここまでの説明を踏まえたタイマーコンポーネントの実装です。

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

type TimerProps = {
  initialCount?: number;
};

const DEFAULT_COUNT = 60;

const Timer: FC<TimerProps> = ({ initialCount = DEFAULT_COUNT }) => {
  const [secondsLeft, setSecondsLeft] = useState(initialCount);
  const tick = (): void => setSecondsLeft((current) => current - 1);
  const resetTimer = (): void => setSecondsLeft(initialCount);

  useEffect(() => {
    if (secondsLeft === 0) setSecondsLeft(initialCount);
  }, [secondsLeft, initialCount]);

  useEffect(() => {
    const timerID = setInterval(() => {
      tick();
    }, 1000);
    return () => clearInterval(timerID);
  }, []);

  return (
    <div>
      <div>
        <div>Count</div>
        <div>{secondsLeft}</div>
      </div>
      <button onClick={resetTimer}>
        Reset
      </button>
    </div>
  );
};

export default Timer;

:pencil: [参考] クラスコンポーネント

昔のクラスベースのReactコンポーネントで記載された場合

import React, { Component } from 'react';

type TimerProps = {
  initialCount?: number;
};

type TimerState = {
  secondsLeft: number;
};

const DEFAULT_COUNT = 60;

class Timer extends Component<TimerProps, TimerState> {
  timerID?: NodeJS.Timeout;

  static defaultProps = {
    initialCount: DEFAULT_COUNT,
  };

  constructor(props: TimerProps) {
    super(props);
    this.state = {
      secondsLeft: props.initialCount || DEFAULT_COUNT
    };
  }

  componentDidMount() {
    this.startTimer();
  }

  componentDidUpdate(prevProps: TimerProps, prevState: TimerState) {
    if (this.state.secondsLeft === 0) {
      this.resetTimer();
    }
  }

  componentWillUnmount() {
    this.stopTimer();
  }

  startTimer = () => {
    this.timerID = setInterval(() => {
      this.tick();
    }, 1000);
  }

  stopTimer = () => {
    if (this.timerID) {
      clearInterval(this.timerID);
    }
  }

  tick = () => {
    this.setState((prevState) => ({
      secondsLeft: prevState.secondsLeft - 1
    }));
  }

  resetTimer = () => {
    this.setState({
      secondsLeft: this.props.initialCount || DEFAULT_COUNT
    });
  }

  render() {
    return (
      <div>
        <div>
          <div>Count</div>
          <div>{this.state.secondsLeft}</div>
        </div>
        <button onClick={this.resetTimer}>
          Reset
        </button>
      </div>
    );
  }
}

export default Timer;

まとめ

... 書くことないので以上です!

0
1
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
0
1