対象読者
- JavaScriptとWeb開発の基礎に理解がある方
- Reactに興味/関心があり、これから学び始める方
前提環境
- macOS Sierra 10.12
- Node.js v6.6.0/npm 3.10.3
- React 15.4.0
PropsとState
Reactコンポーネント内では、PropsとStateという2つのオブジェクトが利用されます。Propsは、コンポーネントを生成するときに親から渡されるオブジェクトで、コンポーネントが画面から取り除かれるまで、不変の値を保持します。対してStateはコンポーネント内で保持される、プライベートなオブジェクトで、可変の変数を保持します。
以下のサンプルは、180秒間のカウントダウンタイマーです。こちらでPropsとStateの使い分けのイメージを説明します。
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; // Reactコンポーネントクラス「Timer」を宣言 class Timer extends React.Component { constructor(props) { // (4) super(props); this.state = {remaining : this.props.seconds}; // (2) } // state.remainingが正の数なら1秒減じる関数 countDown() { if(this.state.remaining > 0) { this.setState((prevState) => ({ remaining : prevState.remaining - 1 // (3) })); } } // 初期化時に、countDownメソッドを1秒ごとに呼び出すタイマーを設定 componentDidMount() { // (5) this.interval = setInterval(() => this.countDown(), 1000); } // 終了処理として、タイマーをクリアする componentWillUnmount() { // (6) clearInterval(this.interval); } // Timerコンポーネントが描画する要素を記述 render() { return ( <div> <h1>Hello, {this.props.name}!</h1> <h2>{this.state.remaining} seconds remaining.</h2> </div> ); } } // Propsを通してnameとsecondsを渡して、Timerコンポーネントを生成 const element = <Timer name="Filange" seconds={180} />; // (1) // index.htmlのid=‘root’をもつ要素にelementを挿入 ReactDOM.render( element, document.getElementById('root') );
Propsとして、ユーザー名とカウントする秒数を設定しています(1)。このように、一度コンポーネントを生成したら(今回の場合、カウントダウンをスタートしてからは)変化しないような値はPropsで扱います。一方、カウントダウン中の秒数は動的に変化し、刻々と画面に変化が反映されます。コンポーネント生成後にも動的に変化する変数はStateとして扱います(2)。本サンプルでは、コンポーネント生成時にintervalを設定して1秒ごとにcountDownメソッドを呼ぶことでstate.remainingを更新し、カウントダウンタイマーを実現しています。
State更新時の注意点
Stateの更新について補足します。直接Stateを更新することはNGです(3)。下記のNGコードのように直接更新した場合、Viewに更新が反映されませんので必ずthis.setState()を呼ぶようにしてください。
// NGコード this.state.name = ‘Ken’; // 正しいコード this.setState({ name : ‘Ken’ });
また、setState()メソッドはアロー演算子を使うことで、第1引数に前の状態のstate、および第2引数にpropsを受け取ることができます。このメソッドは非同期に処理が行われるため、更新がうまく画面に反映されないような場合は、その点を疑ってみるといいかもしれません。
+++スクリプト+++ this.setState((prevState, props) => ({ currentTime: props.format + prevState.date }));
コンポーネントのライフサイクル
Reactコンポーネントのクラス型で宣言する場合、必ずReact.Componentクラスを継承します。React.Componentクラスは抽象クラスであり、継承したサブクラスはライフサイクルメソッドを利用することができるようになります。前節のサンプルでもconstructorやcomponentDidMountといったメソッドを特に説明なく利用していましたが、これらはReactComponentクラスが規定するライフサイクルメソッドです。下図はメソッドの一覧を表しています。
Reactコンポーネントのライフサイクルには、大きく分けて3つのフェーズがあります。コンポーネントが生成されてDOMに要素が挿入される時(Mountingフェーズ)、PropsやStateの値に変更が発生した時(Updatingフェーズ)、コンポーネントがDOMから取り除かれる時(Unmountingフェーズ)です。そして、各フェーズ内で、図の上から順番にメソッドが呼ばれます。
前節において、constructor内でpropsを受け取ってpropsとstateを初期化する処理を行っていましたが、これはコンポーネント生成時の最初に呼ばれるライフサイクルメソッドによるものでした(4)。さらにDOM挿入が完了したタイミングでcomponentDidMountが呼ばれてタイマーを設定し(5)、DOMから取り除かれる際の処理としてタイマーをクリアしています(6)。このようにイベントの設定と解除をcomponentDidMountとcomponentWillUnmountで行うことはよくある実装なので、一つの型として覚えておいてもいいかもしれません。
# | メソッド名 | 引数 | 概要 |
---|---|---|---|
1 | render() | - | React.Componentクラスに必須のメソッド。単一のReact要素を返す役割をもつが、何も描画する必要が無い場合にはnullやfalseを返すこともできる。 |
2 | constructor() | - | DOM挿入前の初期化時に呼ばれる。 |
3 | componentWillMount() | - | renderメソッドによるDOM挿入直前に呼ばれる。(Mountingフェーズ) |
4 | componentDidMount() | - | renderメソッドによるDOM挿入直後に呼ばれる。(Mountingフェーズ) |
5 | componentWillReceiveProps() | nextProps | 親コンポーネントから新しいpropsを受け取ったときに呼ばれる。propsが更新されたときのみに呼ばれ、stateが更新されただけでは呼ばれることはない。 |
6 | shouldComponentUpdate() | nextProps,nextState | propsかstateが更新されたときに呼ばれ、初回の描画時(Mountingフェーズ)では呼ばれない。 |
7 | componentWillUpdate() | nextProps,nextState | renderメソッドによるDOM挿入直前に呼ばれる。(Updatingフェーズ) |
8 | componentDidUpdate() | prevProps,prevState | renderメソッドによるDOM挿入直後に呼ばれる。(Updatingフェーズ) |
9 | componentWillUnmount() | - | React要素がDOMから取り除かれるときに呼ばれる。 |
上の表はReactコンポーネントの各ライフサイクルメソッドを一覧化したものです。全てのメソッドの詳細は割愛させていただきますが、重要なポイントとしていくつか説明します。全メソッドの詳細が知りたい方は公式リファレンスをご参照ください。
まず、[2]constructorはコンポーネントとしてメソッドとして必須ではありませんが、propsを扱う場合には必須のメソッドです。というのも下記コードのように、constructor内でsuper(props)を呼んで初期化する必要があるからです。また、constructor内でthis.propsにアクセスするとundefinedとなるため、propsはconstructorよりも後の処理で扱います。
constructor(props) { super(props); this.state = { date : new Date() }; }
次に[5]から[8]のUpdatingフェーズで呼ばれる各メソッドについてですが、それぞれがこれから保持することになるPropsとState、または直前まで保持されていたPropsとStateを引数としてとります。PropsとStateの前後関係を比較してロジックを組むことができます。例えば、[5]componentWillReceivePropsではPropsの値に変化がなかったとしても、親コンポーネントからPropsを受け取った場合には必ず呼ばれるため、Propsの中身を前後で比較して、値に変化がない場合には更新しないといった具合です。
また、Reactコンポーネントでは親のコンポーネントが更新されるとその子のコンポーネントも全て更新処理が走ります。shouldComponentUpdate内で、falseを返すことでその処理をキャンセルできることも覚えておくといいと思います(デフォルトではtrueが返る)。この処理に関してはjQueryのstopPropagation()の逆をイメージすると分かりやすいかもしれません。