Help us understand the problem. What is going on with this article?

【ajaxを卒業したい】rails+jqueryをreactで書き換えてみた #1

More than 1 year has passed since last update.

速度改善の一環で今まで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つが候補に挙がりました。

候補

SuperAgent
Fetch API
axios

何を使っても良かったのですが、今回は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とか言われたのでインポートしたという経緯があります。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away