ユーザー操作と非同期的にAjaxでデータを取得するケースもよくありますが、Reactと組み合わせようとしたときに少し悩むこととなりました。
Ajaxロードだけど悩まない例
「ボタンを押す」→「あるJSONデータを取ってくる」→「とってきたJSONデータをもとに、ポップアップウィンドウを開く」というような動作を考えてみます。
class SimpleComponent extends React.Component {
handleClick = () => {
fetch('some.json').then(data => {
// 実際にはもっと複雑な処理だけど、解説のためごく簡単に
alert(data.str);
});
}
render() {
return <button onClick={handleClick}>ボタン</button>;
}
}
この場合、fetch
でとってきたデータはそのままthen
で処理されるだけなので、Reactとは何も関係せず処理が完結します。
Ajaxロードしたデータを、コンポーネントの「状態」にしたい
とはいえ、この方法では取得したデータが完全にReactの外の世界に置かれたままとなるので、「ロードしたデータをReactの表示の上でも活用したい」とか、「ボタンクリック以外のタイミングでデータの取得を駆動したい」という目的ではうまく使えません。
そして、コンポーネントが状態を持つときはstate
に入れるものなので、データはstate
に持たせることにしましょう。なお、Promise
はそれ自体の状態が破壊的に変化しますので、state
に直接入れるとうまく動かないことが見込まれます。
doFetch() {
if(this.getterPromise) return;
this.getterPromise = fetch('some.json').then(
data => this.setState({data});
);
}
Ajaxロードとボタンクリックを連動させる
これでデータを取れるようにはなりますが、ボタンとの同期はまた考えないといけません。
- ボタンを押したあとにロードが完了した場合→ロード完了の時点でポップアップを表示
- ロード完了したあとにボタンを押した場合→ボタンを押した時点でポップアップを表示
どうしようか考えたところで、「ボタンを押したときにはフラグだけ立てて、componentDidUpdate
でフラグとデータが揃っているかチェック」という形にすることにしました。なお、今後のReactでは非同期でレンダリングを行うこととなるため、componentDidUpdate
以外のコールバックは複数回呼ばれうることになる、とのことです。
componentDidUpdate() {
if(this.state.data && this.state.willShowPopup) {
alert(this.state.data);
this.setState({willShowPopup: false});
}
}
handleClick = () => {
this.setState({willShowPopup: true});
}
Reactは枠組みがしっかり決まったライブラリですが、その中でも充分なことが可能です。枠をはみ出せば、一気に茨の道となります。