速度改善の一環で今までjqueryとrailsで実装していたページをreactに書き換えました。その時色々キャッチアップしたのでメモがてら残しておきます。悩んだところと個人的にあっそうなのねと思ったところ(以下の項目)について主に書いていきます。
悩んだところ
・jqueryを卒業するためにapi部分でajax使いたくない
・スクロール位置によるオートロードさせたい
・親子でイベントを保持させたい
・setStateの時、keyを変数にしたい
あっそうなのねと思ったところ
・イベントターゲットのidってそこなんだ
・renderは一つしか返せないのか
・stateのタイミング
jqueryを卒業するためにapi部分でajax使いたくない
最初apiの部分だけjqueryに頼ろうかなと思って、ajaxを使っていました。
下記は初期値のデータを一旦renderさせた後にapiで取得し再描画しています。
import React, { Component } from 'react';
import $ from "jquery";
class Main extends Component {
constructor() {
super();
this.state = {
data: "",
params:{
name: "",
mail: ""
}
}
}
componentDidMount() {
this.reconstructionData();
}
reconstructionData() {
$.ajax({
type: "POST",
dataType: "json",
url: "/apis/reconstruction_data",
data: this.state.params
}).done(function(data){
this.setState({
data: data
});
}).fail(function(){
})
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
ReactDOM.render(
<Main />,
document.querySelector('#js-main')
);
ただ冒頭で述べましたが、reactはここのページでしか使っていません。他のページはゴリゴリjqueryを使っています。他のページで使用しているjqueryのバージョンとは異なってしまうのは避けたい。またajaxのためだけにjquery入れるのもな。それにreactのような仮想DOMの概念の上では、DOMの直接操作は推奨されない・・・だったらと思い、AJAX専用のライブラリにまかせてしまうことにしました。
調べたところ、以下3つが候補に挙がりました。
候補
何を使っても良かったのですが、今回はFetch APIを使用することにしました。理由は消去法です。まず SuperAgentは便利であるが、今後のWeb標準化(Extensible Web)の展開を考えると、whatwgで議論されている「Fetch API」を使うといいかもと思ったからです。これによりSuperAgentが候補から外れます。次にaxiosですがこれはまだ設定できる項目がsuperagentと比べて少なかったりするみたく、何かあった時にめんどくさいなと思い候補から外しました。
さて導入してみます。githubに書いてありますが。。。
npmでインストール。
npm install whatwg-fetch --save
importして使う。fetchの使い方はこちらから。とりあえず一旦置き換えてみます。
import React, { Component } from 'react';
import 'whatwg-fetch'
class Main extends Component {
constructor() {
super();
this.state = {
data: "",
params:{
name: "",
mail: ""
}
};
}
componentDidMount() {
this.reconstructionData();
}
reconstructionData() {
fetch('/apis/reconstruction_data', {
method: 'POST',
mode: 'cors',
credentials: 'include',
body: this.state.params
}).then(function(response) {
return response.json()
}).then(function(data) {
this.setState({
data: data
});
});
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
ReactDOM.render(
<Main />,
document.querySelector('#js-main')
);
さてここで問題がまた発生します。apiに意図した変数を渡せない・・・。
使い方ガイドによるとJavaScriptの値をJSON文字列に変換してbodyに渡せばいけるらしい。
body: JSON.stringify({
firstParam: 'yourValue',
secondParam: 'yourOtherValue',
})
今回はstateでapiで渡す条件を持っているのでこんな感じ?
import React, { Component } from 'react';
import 'whatwg-fetch'
class Main extends Component {
constructor() {
super();
this.state = {
data: "",
params:{
name: "",
mail: ""
}
};
}
componentDidMount() {
this.reconstructionData();
}
reconstructionData() {
fetch('/apis/reconstruction_data', {
method: 'POST',
mode: 'cors',
credentials: 'include',
body: JSON.stringify({
name: this.state.name,
mail: this.state.mail,
})
}).then(function(response) {
return response.json()
}).then(function(data) {
this.setState({
data: data
});
});
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
ReactDOM.render(
<Main />,
document.querySelector('#js-main')
);
これでも恐らくいいのですが、いちいち指定した持たせるのも面倒くさいです。どうせならparamsというので渡せたら便利じゃね?と思い、いろいろ調べてみました。結果、以下で解決しました。
url-search-paramsというurl操作するライブラリをnpmでインストール。
npm install url-search-params
importする。
import 'url-search-params-polyfill'
使い方はこんな感じ
const params = new URLSearchParams();
params.append("params", JSON.stringify(this.state.params));
なので最終的に今回のはこうなります。
import React, { Component } from 'react';
import 'whatwg-fetch'
class Main extends Component {
constructor() {
super();
this.state = {
data: "",
params:{
name: "",
mail: ""
}
};
}
componentDidMount() {
this.reconstructionData();
}
reconstructionData() {
const params = new URLSearchParams();
params.append("params", JSON.stringify(this.state.params));
fetch('/apis/reconstruction_data', {
method: 'POST',
mode: 'cors',
credentials: 'include',
body: params
}).then(function(response) {
return response.json()
}).then(function(data) {
this.setState({
data: data
});
});
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
ReactDOM.render(
<Main />,
document.querySelector('#js-main')
);
ちなみにapiの方では
params[:params] = JSON.parse( params[:params] )
と受け取ったパラメタをパースしてparams[:params]として代入しておけば既存コードを書き換えなくても扱えます。
かなり長くなったのでこれを第一弾として一旦切ります。
おまけ
Fetch APIについて
Fetch APIは記述を見ていただければわかるとおり、「Promiseベースの設計で、結果はPromise」で返されます。また今回何気なく結構なオプションを勝手につけてしまっています。
fetch('/apis/reconstruction_data', {
method: 'POST', ←こことか
mode: 'cors', ←こことか
credentials: 'include', ←こことか
body: params ←こことか
}).then(function(response) {
return response.json()
}).then(function(data) {
this.setState({
data: data
});
});
少しだけまとめがてら説明を加えておきます。
fetchの基本的な使い方
fetch('URLもしくは相対パス').then(...);
なので今回のを例にとるとこんなかんじ。
fetch('/apis/reconstruction_data').then(function(response) {
return response.json();
}).then(function(data) {
.........
});
fetch()の結果はPromiseで返され、resolve関数には引数としてResponseオブジェクトが渡されます。ここで、取得した内容はBlob, ArrayBuffer, JSON, プレーンテキストのいずれかの形で受け取ることが出来るみたいです。今回はJSON形式で受け取りたかったのでresponse.json();
という風に指定してます。
オプションをつける
オプションを渡すことで、HTTPリクエストを柔軟にカスタマイズできるようになっておりJSON形式でそれを指定していきます。この辺はajaxと似ているのでさほど違和感ないかなといった個人的感想です。
オプションは様々です。リンク先をみながら対応していただければと思います。ここでは今回使用しているオプションについて説明します
fetch('URLもしくは相対パス', {***JSONでオプションを指定 ***}).then(...);
見にくいので改行します。
fetch('/apis/reconstruction_data', {
***JSONでオプションを指定 ***
}).then(function(response) {
return response.json();
}).then(function(data) {
.........
});
だんだん近づいてきていますね笑。
methodについて
オプションのmethodにはHTTPメソッド名/リクエストするメソッド(GET、POSTなど)を指定することが出来ます。また、メソッドが'POST'の場合は、bodyメンバにアップロードしたいデータを指定することができます。
今回はデータを渡したかったのでpostを使っています。
fetch('/apis/reconstruction_data', {
method: 'POST'
}).then(function(response) {
return response.json()
}).then(function(data) {
this.setState({
data: data
});
});
modeについて
リクエストで使いたいモード(cors、no-cors, or same-origin)です。CORSによるクロスオリジン接続を行いたいときはオプションのmodeメンバにcorsを指定して使います。個人的には使うタイミングあるのか疑問に思いますが、same-originを指定すると同一オリジン以外の接続がエラーと扱いになります。no-corsを指定するとクロスオリジン接続ができない場合にエラーとならずに空のレスポンスが返されるみたいです。
fetch('/apis/reconstruction_data', {
method: 'POST',
mode: 'cors'
}).then(function(response) {
return response.json()
}).then(function(data) {
this.setState({
data: data
});
});
基本考えなくて良いかなと思いますが、一応サーバーの設定のリンクだけ載せておきます。CORSまとめ
credentialsについて
デフォルトではクッキー等のクレデンシャルをHTTPリクエストに含めないようになっています。omit、same-origin、include が使えるみたいです。クッキーを付けたい場合は、include使います。
fetch('/apis/reconstruction_data', {
method: 'POST',
mode: 'cors',
credentials: 'include'
}).then(function(response) {
return response.json()
}).then(function(data) {
this.setState({
data: data
});
});
bodyについて
リクエストに追加したいボディ、Blob、BufferSource、FormData、URLSearchParams もしくは USVString オブジェクトなどが使用できます。methodのところでも軽く触れましたがメソッドの指定がGETやHEADの場合使用できませんので注意して下さい。
今回はURLSearchParamsを使用しています。URLSearchParamsに関してはこちらから。基本は今回の形さえ頭に入れとけば問題ないかなと思ってます。
const params = new URLSearchParams();
params.append("params", JSON.stringify(this.state.params));
としてからbodyに渡しています。
fetch('/apis/reconstruction_data', {
method: 'POST',
mode: 'cors',
credentials: 'include',
body: params
}).then(function(response) {
return response.json()
}).then(function(data) {
this.setState({
data: data
});
});
今回の形に無事なりました。
ブラウザサポート状況
import 'whatwg-fetch'
とインポートしているので基本気にしなくて良いかと思っています。実は最初インポートせずにchrome環境では使えていたので、インポートせずに行っていました。safariで最後確認したところundefinedとか言われたのでインポートしたという経緯があります。