はじめに
Fullstack React: 30 Days of React の意訳です。
ちょいちょい読んでるのでせっかくなので訳そうと思った次第。飽きるまで続きます。かなり意訳です。
- Day1 - What is React?
- Day2 - What is JSX?
- Day3 - Our First Components
- Day4 - Complex Components
- Day5 - Data-Driven Components
- Day6 - State
※補足: ってなってるのは、私の補足や思ったことなどです
本編
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コンポーネントを改良してみましょう。
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コンポーネントに追加してみましょう。
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を文書化する方法を見ていきます。