問題提起
手続き的に書くとバグりやすいから宣言的に書きたいよね、というのが最近のトレンドですよね。ReactはそれをWebのクライアントサイドで大きく改善させてくれました。
そのReactをもってしても宣言的に書けない処理(≒サーバーとのデータのやりとり)をどう記述すればよいかについてまとめます。
手続き的→宣言的のざっくりした経緯
Webクライアントの処理は大きく、
- DOM以外の外部への影響(logとかwindowとか)
- 外部へ影響を及ぼさない演算
- DOM操作
- サーバーとの通信
に分けられると思います。
静的ページ全盛期の頃は知りませんが、多くのJavascriptエンジンが生まれてきて処理性能が上がってきた最近では、1や2はほとんどの場合において(時間的に)無視できるレベルまで到達していると言っていいでしょう。
Reactが登場してからは、仮想DOMというものがDOM操作を最小に済ませることで、3の処理を(時間的に)無視できるレベルにしてくれました。
(もちろん変化量自体が大きい場合には無視できなくなるのですが。。。)
となると、残るは4のサーバーとの通信です。こいつだけはどうしても無視できないでしょう。
実装サンプル
例えば、住所の入力フォームで、県をselectBoxから選ぶと市のselectBoxの中身がajaxで差し替わるという例で考えてみます。
具体的には、県コンポーネントの選択した値が市コンポーネントのpropsとなる形です。
良くない例
理想の状態は宣言的に書くことなので、renderメソッドの中にサーバー通信を書くことを試みます。
同期的に書く場合
たいていの場合サーバーとの通信に数十ms〜数百msはかかるので、その間画面が固まってしまうのはイケてないです。
(今どきのフロントエンジニアでこんな風に書く人はいないと思いますが。。。)
render{
var data = syncAccess(this.props.prefectureId)
return <Selectbox value={data}>
}
非同期的に書く場合
「じゃぁ非同期で書くか」と言った場合の問題点は、(ロジック的に)renderの処理と別のスレッドになってしまい返り値をそのまま使えないことです。(callbackでrenderを読んだら無限ループなので。)
仕方ないのでstateの中に取得した値を格納することにします。すると、stateの状態が変わったので再度renderが走ります。
このままではやっぱり無限ループになってしまいます。。。
render: function(){
asyncAccess(this.props.prefectureId).then(function(res){
this.setState({data: res.data});
});
return <Selectbox value={this.state.data}>
}
非同期的に書く場合(ただし通信は最小限に)
無限ループしないでサーバー通信を行うには、前回とpropsの値が変わっていなければサーバー通信を行わない、というロジックにしないといけません。
仕方ないので、非同期処理の後にstateを更新させるタイミングで、そのときのpropsの値も一緒に格納しておくことにしましょう。それで、renderメソッドの中で条件分岐させればいいよね。
render: function(){
if(this.state.prefectureId !== this.props.prefectureId){
asyncAccess(this.props.prefectureId).then(function(res){
this.setState({data: res.data, prefectureId: res.prefectureId});
});
}
return <Selectbox value={this.state.data}>
}
...うわぁ、汚いですね。。。
しかも一歩間違えれば途端に無限ループを起こす危険コードです。
正しい例
componentWillReceiveProps: function(nextProps) {
if(this.props.prefectureId === nextProps.prefectureId){return;}
asyncAccess(nextProps.prefectureId).then(function(res){
this.setState({data: res.data});
});
},
render: function(){
return <Selectbox value={this.state.data}>
}
componentWillReceiveProps使う。
(propsが変化しないときも発火してしまうので、頭に条件分岐を書かなきゃいけない。)
さっきのコードよりだいぶスッキリしたと思います。renderの中に変な処理が挟まらないので無限ループの心配も減りました。
結論
renderメソッドの中身は宣言的に書くべき。言い換えれば、時間のかかる処理や無限ループになりうる処理は書くべきではない。
それだけじゃ足りない場合は、reactが何かしらのAPIを用意してくれてるのでそれを使おう。