LoginSignup
0
2

More than 5 years have passed since last update.

30 Days of React を意訳していく Day7 - Lifecycle Hooks

Posted at

はじめに

Fullstack React: 30 Days of React の意訳です。

ちょいちょい読んでるのでせっかくなので訳そうと思った次第。飽きるまで続きます。かなり意訳です。

※補足: ってなってるのは、私の補足や思ったことなどです

本編

Lifecycle Hooks

Day7ではReactで使えるLifecycle Hooksについて見ていきます。これらがなぜ便利なのか、どんなときに使うべきなのを説明していきます。

Day6まででいろいろなことを学びReactの基礎は習得できたと思います。今回は実装から少し離れて、コンポーネントがアプリの中でどのように生きているのか、つまり、コンポーネントのライフサイクルについて説明します。

Reactがアプリをマウントするとき、様々なタイミングで実行されるフックを提供してくれます。フックを使うためには、コンポーネント上に適切な関数を定義する必要があります。それでは見ていきましょう。
※補足: 他のフレームワークでもある、will(before)XXXX、did(after)XXXX、のようなフックの話です。

componentWillMount() / componentDidMount()

Reactでは仮想DOMを使っているので、ページロード直後にDOM Readyになっている想定で処理を書くことができません。マウント前後に実行したい処理がある場合は、2つの関数 componentWillMount() / componentDidMount() が使えます。

マウントとは?
Reactでは実際のDOMではなく仮想表現によってDOMを扱っていて、Reactの管理しているメモリ内にそれらを管理しています。私たちがマウントという言葉を使う時は、この仮想DOMを実際のDOMに変換する作業のことを指します。

これはデータを取得して表示するようなコンポーネントを作るときに役立ちます。ずっと使ってきたアクティビティトラッカーを使って、githubのアクティビティを表示してみましょう。コンポーネントが表示さえるときだけに、データがロードされるようにしたいですよね。

アクティビティリストのコードを思い出してください。

class Content extends React.Component {
  render() {
    const {activities} = this.props; // ES6 destructuring

    return (
      <div className="content">
        <div className="line"></div>

        {/* Timeline item */}
        {activities.map((activity) => (
          <ActivityItem
            activity={activity} />
        ))}

      </div>
    )
  }
}

github.com events api からデータを取得して、stateにデータをいれてそれらを表示するようにContentコンポーネントを改良してみましょう。

4836c34636f46f40f29f6d7cce40c049.png

Day6でやったように、stateを使ってコンポーネントをステートフルにしていきます。

class Content extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      activities: []
    }
  }

  // ...
}

ここで、コンポーネントの準備が整ったら、HTTPリクエストを飛ばしたいと思うはずです。componentWillMount()(またはcomponentDidMount())をコンポーネントに定義することによって、ReactはDOMにマウントする直前にメソッドを実行します。これはGETリクエストするのにベストな場所です。

ではHTTPリクエストを飛ばすようにコンポーネントを更新しましょう。小さなリストを表示したいだけなので、4つだけ取得します。

数日後にAJAXリクエストについては学ぶので、いったん静的なjsonデータがあるものとして、実装に集中しましょう。

class Content extends React.Component {
  // ...
  componentWillMount() {
    this.setState({activities: data});
  }
  // ...
}

コンポーネントから何も変更を加えなくても動作することに気をつけてください。

componentWillUpdate() / componentDidUpdate()

レンダリングの直前もしくは直後になにかしらの処理を行いたい場合があります。例えば、コンポーネントのpropsが変更されたときに、特定の関数セットを実行したいときなどです。そのような場合に、componentWillUpdate()は適切なフックです。(内部でthis.setState()を呼び出すと無限ループになるので注意)

componentWillReceiveProps()

新しいpropsを受け取ったときに呼ばれるメソッドです。これはコンポーネントが新しいpropsを受け取ったときに呼ばれる最初のメソッドです。特定のpropsに基づいてstateの値を設定したり更新したりするのに使うことができます。

ここで注意すべきは、 componentWillReceiveProps() が呼ばれてもpropsの値が変更されているとは限らないというこです。値が変更されているかチェックするのはよい考えです。

例として、リフレッシュボタンをContentコンポーネントに追加してみましょう。

787c6badf389dfa54bb52149666c9390.png

componentWillReceiveProps() を使ってデータの再読込をコンポーネントに依頼します。このコンポーネントはステートフルなので、コンポーネントのpropsを更新するのではなく、stateを使って更新するようにしたいです。componentWillReceiveProps()を使ってデータを更新したいことがコンポーネントに伝えることができます。

コンテンツ要素を更新するように指示する、requestRefreshとうboolean props をContentコンポーネントに渡すボタンを追加してみましょう。

class Container extends React.Component {
  constructor(props) {
    super(props);

    this.state = {refreshing: false}
  }

  // Bound to the refresh button
  refresh() {
    this.setState({refreshing: true})
  }

  // Callback from the `Content` component
  onComponentRefresh() {
    this.setState({refreshing: false});
  }

  render() {
    const {refreshing} = this.state;
    return (
      <Panel>
        <Header title="Github activity" />
        {/* refreshing is the component's state */}
        <Content
          onComponentRefresh={this.onComponentRefresh.bind(this)}
          requestRefresh={refreshing}
          fetchData={fetchEvents} />
        {/* A container for styling */}
        <Footer>
          <button onClick={this.refresh.bind(this)}>
            <i className="fa fa-refresh" />
            Refresh
          </button>
        </Footer>
      </Panel>
    )
  }
}

新しいrequestRefreshプロパティを使って、値を更新することができます。

class Content extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false, // <~ set loading to false
      activities: []
    }
  }

  // Update the data when the component mounts
  componentDidMount() {
    this.updateData();
  }

  componentWillReceiveProps(nextProps) {
    // Check to see if the requestRefresh prop has changed
    if (nextProps.requestRefresh !== this.props.requestRefresh) {
      this.setState({loading: true}, this.updateData);
    }
  }

  // Call out to github data and refresh directory
  updateData() {
    this.setState({
      loading: false,
      activities: data
    }, this.props.onComponentRefresh);
  }

  render() {
    const {loading, activities} = this.state;

    return (
      <div className="content">
        <div className="line"></div>
        {/* Show loading message if loading */}
        {loading && <div>Loading</div>}
        {/* Timeline item */}
        {activities.map((activity) => (
          <ActivityItem
            activity={activity} />
        ))}

      </div>
    )
  }
}

※補足1: ちょっとずつソースが長くなって混乱してくると思いますが、要は親コンポーネントから子コンポーネントに何か伝える場合は必ずpropsを使えという思想で、そのpropsの変化に呼応して、子コンポーネントのstateを変化させる場合に上記のコールバックを使えってことです。

※補足2: さらっと書かれていますが、onComponentRefreshという子コンポーネントから呼ばれる、コールバック関数が導入されてます。propsで値を子に私、何か子から返したい場合は、コールバック関数を使うという感じっぽいですね。

componentWillUnmount()

コンポーネントがアンマウントされる前に、ReactはcomponentWillUnmount()を呼び出します。 ここで、タイムアウトのクリア、データのクリア、Webソケットの切断など、必要な可能性のあるクリーンアップ処理を実行すると良いでしょう。

例えば、前回のクロックコンポーネントでは、毎秒呼び出されるタイムアウトを設定しました。 コンポーネントがアンマウントする準備ができたら、このタイムアウトをクリアして、すでに存在しないコンポーネントのタイムアウトを継続しないようにします。

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = this.getTime();
  }

  componentDidMount() {
    this.setTimer();
  }

  setTimer() {
    this.timeout = setTimeout(this.updateClock.bind(this), 1000);
  }

  updateClock() {
    this.setState(this.getTime, this.setTimer);
  }

  getTime() {
    const currentTime = new Date();
    return {
      hours: currentTime.getHours(),
      minutes: currentTime.getMinutes(),
      seconds: currentTime.getSeconds(),
      ampm: currentTime.getHours() >= 12 ? 'pm' : 'am'
    }
  }

  // ...
}

クロックコンポーネントがアンマウントされるとき、コンポーネントで設定したsetTimer()をクリアします。 componentWillUnmount()で、このクリーンアップを処理しましょう。

class Clock extends React.Component {
  // ...
  componentWillUnmount() {
    if (this.timeout) {
      clearTimeout(this.timeout);
    }
  }
  // ...
}

いくつかのライフサイクルフックについて説明しました。実際にアプリを作る際に、これらを良く使うことになるので、これらのメソッドについてよく知り、Reactアプリの中でどのような役割をしていくのかを良く知っておくと良いでしょう。

このセクションでは、Reactのコールバックとそれにこじつけ子コンポーネントから呼ばれる親コンポーネントのコールバックについて学びました。Day8では、チームやアプリケーション間でコンポーネントを共有するときの、コンポーネントのprops APIを文書化する方法を見ていきます。

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