引き続きReact
[Get started]から始めるReact開発:その1
[Get started]から始めるReact開発:その2:Rendering Elements
[Get started]から始めるReact開発:その3:Components and Props
[Get started]から始めるReact開発:その4(State and Lifecycle①
[Get started]から始めるReact開発:その4(State and Lifecycle②
あまりに長かったので分割しました。
##Adding Lifecycle Methods to a Class(クラスにライフサイクルメソッドを加える
多くのコンポーネントを含むアプリケーションでは、コンポーネントが破棄される時に合わせてリソースの開放をすることが非常に重要です。
Clock
は最初にDOMにレンダリングされるときに毎回タイマーを設定しなければいけません。これをReact
ではmounting
と呼びます。
またClock
によって作られたDOMが削除された時にタイマーをかいほうしなければいけません。これをReact
ではunmounting
と呼びます。
コンポーネントがmounts
やunmounts
する時に幾つかのコードを実行するための特別なメソッドを宣言できます。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
これらのメソッドはlifecycle hooks
と呼ばれています。
componentDidMount()
フックはDMOMにコンポーネントがレンダリングされたあとに実行されます。これはタイマーをセットするのに最適です。
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
this
にtimerIDを保存する方法については注意してください。
this.props
はリアクトによって設定され、this.state
は特別な意味を持ちますが、視覚的なアウトプットに使用されないようなデータを保存する必要がある場合はクラスに手動で追加のフィールドを加えることは自由にできます。
render()
でそのデータが使われない場合は、stateにはないほうがよいでしょう。
componentWillUnmount()
ライフサイクルフックではタイマーを破棄します。
componentWillUnmount() {
clearInterval(this.timerID);
}
最後に、毎秒実行するメソッドのtick()
を実行します。
コンポーネントのlocal state
の更新を調整するためにthis.setState()
を使用します。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
現在時計は毎秒刻みで動きます。
これらがどのように動いているのかということとメソッドが呼ばれる順番をおさらいしましょう。
-
<Clock />
がReactDOM.render()
に渡された時、リアクトはClock
コンポーネントのコンストラクタを呼び出します。Clock
は現在の時間を表示する必要があるため、現在の時間を含んだオブジェクトでthis.state
を初期化します。 -
Reactは
Clock
コンポーネントのrender()
メソッドを呼び出します。これは何を画面上に表示すべきかをReactに知らせています。Reactはアウトプットに合うようにDOMを更新します。 -
Clock
アウトプットがDOMに入れられた時に、ReactはcomponentDidMount()
を呼び出します。その中では、Clock
コンポーネントが毎秒tick()
を呼ぶためのタイマーの設定をブラウザに対して要求しています。 -
毎秒
tick()
メソッドがブラウザから呼ばれます。その中では、Clock
コンポーネントが現在の時間を含んだオブジェクトでsetState()
を呼び出すことでUI更新を調整します。setState()
呼び出しに感謝をして、Reactはstate
に変更があることを知り、何が画面上に表示されるべきかを再度知るためにrender()
メソッドを呼びます。この時、render()
メソッドの中のthis.state.date
は異なりますし、描画されるアウトプットは更新された時間も含みます。ReactはそれによってDOMを更新します。 -
Clock
コンポーネントがそのうちにDOMから削除された場合は、ReactはcomponentWillUnmount()
を呼び出しタイマーをストップします。
##Using State Correctly
setState()
について知るべきことが3つあります。
###Do Not Modify State Directly(Stateは直接変更できない
例えば、下記はコンポーネントでの再レンダリングができません。
// Wrong
this.state.comment = 'Hello';
代わりにsetState()
を使用します。
// Correct
this.setState({comment: 'Hello'});
this.state
を割り当てることができるただひとつの場所はコンストラクタです。
###State Updates May Be Asynchronous(Stateはの更新は非同期に
Reactはパフォーマンスのために一つの更新の中で複数のsetState()
呼び出しをバッチできます。
this.state
とthis.props
は非同期に更新されるので、次のStateを計算するためにそれらの値を使うべきではない。
例えば、下記コードはカウンターの更新に失敗する。
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
これを修正するためにオブジェクトというよりは関数として受け入れるsetState()
の第二形式を利用します。
この関数は第一引数として前のstateを受け入れ、第二引数として更新が適用された時のpropsを受け入れます。
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
上記はアロー関数を使用しましたが、通常の関数としても利用できます。
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
###State Updates are Merged
ReactではsetState()
を呼ぶ時は、現在のStateの中で渡されたオブジェクトをマージします。
例えば、Stateが独立した複数の変数を含んでいる場合
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
別々のsetState()
呼び出しで個別にそれらを更新することができます。
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
マージが浅いので、this.setState({comments})
はthis.state.posts
についてはそのまま残しますが、this.state.comments
については完全に入れ替えます。
##The Data Flows Down
親でも子でもないコンポーネントは、コンポーネントがステートフルかステートレスかを知ることができますし、関数として定義されているかクラスとして定義されているかどうかを気にする必要はありません。
これがState
がよくカプセル化もしくはローカルと呼ばれる理由です。それをセットしたものやそれ自身以外のコンポーネントからは利用できません。
A component may choose to pass its state down as props to its child components.(ちょっといみわからん、というかここからちょっと英語よくわかんなくなったのでほぼ機械翻訳)
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
これはまた、ユーザー定義コンポーネントとして働きます。
<FormattedDate date={this.state.date} />
FormattedDate
コンポーネントはdate
をpropsで受け入れ、それがClock's state
やClock's props
もしくは手で入力されたものかどうかはしりません。
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
これは一般的にtop-down
もしくはunidirectional
データフローと呼ばれています。全てのstateは常に特定のコンポーネントに所有され、そのstateから派生した全てのデータやUIはツリー内のコンポーネントの下にのみ影響を与えます。
滝のようなものを想像してください。それぞれのコンポーネントStateがそれぞれの水源に流れていくイメージです。
それぞれのコンポーネントは完全に独立しているようにみえるため、3つの<Clock>
をレンダリングしたAppコンポーネントを作ることができます。
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
それぞれのclock
はタイマーのセットと更新を個別に行っています。
React Appsでは、コンポーネントがステートフルかステートレスかどうかは時間とともに変わるかもしれないコンポーネントの詳細な実装を考える必要があります。ステートフルなコンポーネントの内側のステートレスコンポーネントを使うことができますし、その逆もまた可能です。