馬上少年ブログ

無用の備忘録と雑談

FluxのActionCreatorの中でaxiosを使ってAPIを叩く

Reactのfluxアーキテクチャでは、外部APIとのやりとりはActionCreatorのところで行われるとのことです。

今回は無料のOpenWeathermap API を使って、お天気を表示するだけのアプリを作成します。

axiosとは

PromiseベースでHTTP通信を簡単に行うことができるライブラリ。

以下のようにPromiseベースなので簡単にリクエストを生成でき、とてもみやすいです。

const url = 'https://xxx.com'
const params = { hoge: 'fuga' }

axios
  .get(url, { params })
  .then(data => {
    //成功時の処理
    console.log(data)
  })
  .catch(error => {
    //失敗時の処理
    console.log(error)
  })

以下のように設定をオブジェクトで渡すこともできます。パラメータがある時などこっちの方がわかりやすいですね。

const url = 'https://xxx.com'
const params = { hoge: 'fuga' }

axios({
  method : 'GET',
  url    : url,
  params : params
}).then(data => {
  //略
})

post通信の時はpostメソッドを使い、paramsではなくdataを使います。

const url = 'https://xxx.com'
const data = { hoge: 'fuga' }

axios({
  method : 'POST',
  url    : url,
  data   : data
}).then(response => {
  //略
})

環境構築

Nodo.jsをインストールして、プロジェクトディレクトリを作成してください。

まずは必要なパッケージをインストール。

$ npm install react react-dom
$ npm install --save-dev flux @babel/core @babel/preset-env @babel/preset-react babel-loader webpack webpack-cli webpack-dev-server

今回使用するaxiosもインストールしておきます。

$ npm install axios

package.jsonおよびbabel, webpackの設定ファイルは以下のとおり。

package.json

{
  "name": "otenki_app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.18.0",
    "react": "^16.8.1",
    "react-dom": "^16.8.1"
  },
  "devDependencies": {
    "@babel/core": "^7.2.2",
    "@babel/preset-env": "^7.3.1",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.5",
    "flux": "^3.1.3",
    "webpack": "^4.29.3",
    "webpack-cli": "^3.2.3",
    "webpack-dev-server": "^3.1.14"
  }
}

webpack.config.js

module.exports = {
  mode: 'development',
  entry: './src/index',
  output: {
    filename: 'app.js',
    path: __dirname + '/public/js'
  },
  devServer: {
    contentBase: __dirname + '/public',
    port: 8080,
    publicPath: '/js/'
  },
  devtool: 'eval-source-map',
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    }]
  }
};

.babelrc

{
  "presets": [
    [
      "@babel/preset-env", {
        "targets": {
          "node": "current"
        }
      }
    ], 
    "@babel/preset-react"
    ]
}

必要なディレクトリを作っておき、エントリーポイントのsrc/index.jsと、読み込み用のpublic/index.htmlを記述します。

$ mkdir public src src/containers src/data src/Views

src/index.js

import React from 'react'
import ReactDOM from 'react-dom'
import AppContainer from './containers/AppContainer';

ReactDOM.render(
  <AppContainer />,
  document.getElementById('root')
);

src/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="root"></div>
  <script src="js/app.js"></script>
</body>
</html>

Fluxの各部門を実装する

Dispatcher

モジュールをimportしてインスタンス化するだけでOKです。ありがたい。

src/data/Dispatcher.js

import { Dispatcher } from 'flux';

export default new Dispatcher();

Container

ViewとStoreとStateを渡しておけばOK。ほんとうにありがたい。

src/containers/AppContainer.js

import AppView from '../Views/AppView';
import ActionCreator from '../data/ActionCreator'
import { Container } from 'flux/utils';
import OtenkiStore from '../data/OtenkiStore';

const getStores = () => {
  return [
    OtenkiStore,
  ];
};

const getState = () => {
  return {
    otenki: OtenkiStore.getState(),
    onGetOtenki: ActionCreator.getOtenki,
  };
}
export default Container.createFunctional( AppView, getStores, getState );

ActionCreator

今回の肝です。axiosで非同期通信しています。APIは都市名とAPIキーが必要です。APIキーは別途取得しておいてください。

qiita.com

返ってきたデータをActionTypeとともにDispatcherに送ります。ActionTypeは別ファイルで定義しています。

import ActionTypes from './ActionTypes';
import Dispatcher from './Dispatcher';
import axios from 'axios';

const Actions = {

    getOtenki(city) {
    const url = 'https://api.openweathermap.org/data/2.5/weather'
    const params = {
      q: city,
      units: 'metric',
      appid: 'YOUR_API_KEY'
    }

    axios({
      method : 'GET',
      url    : url,
      params : params
    })
    .then((data) => {
      Dispatcher.dispatch({
        type: ActionTypes.GET_OTENKI,
        data: data.data,
      })
    })
    .catch((error) => {
      console.log(`失敗しました:${error}`)
    })
  }

};

export default Actions;

src/data/ActionTypes.js

const ActionTypes = {
  GET_OTENKI: 'GET_OTENKI',
};

export default ActionTypes;

Store

データをそのまま返します。

src/data/OtenkiStore.js

import {ReduceStore} from 'flux/utils';
import ActionTypes from './ActionTypes';
import Dispatcher from './Dispatcher';

class OtenkiStore extends ReduceStore {
  constructor() {
    super(Dispatcher);
  }

  getInitialState() {
    return '';
  }

  reduce(state, action) {
    switch(action.type) {

      case ActionTypes.GET_OTENKI: {
        if(action.data === undefined) {
          return state;
        } else {
          return action.data;
        }
      }

      default: {
        return state;
      }

    }
  }
}

export default new OtenkiStore();

View

東京、大阪、岡山の3都市を用意しました。citiesをmapしてボタンを生成し、valueの都市名をコールバックの引数としてそのままActionに渡します。この都市名はAPIを叩くときに使用しています。

Views/AppView.js

import React from 'react'

const AppView = props => (
  <div>
    <OtenkiBtn {...props} />
    <OtenkiView {...props} />
  </div>
)

const cities = [ 'Tokyo', 'Osaka', 'Okayama' ]

const OtenkiBtn = props => {
  const onGetOtenki = (e) => props.onGetOtenki(e.target.value);
  return (
    <div>
      {cities.map(city => (
        <button key={city} onClick={onGetOtenki} value={city}>{city}</button>
      ))}
    </div>
  )
}

const OtenkiView = props => {
  if(props.otenki) {
    return (
      <div>
        <p>{props.otenki.name}の天気</p>
        <div>{props.otenki.weather.main}</div>
        <div>最高気温:{props.otenki.main.temp_max}&deg;C</div>
        <div>最低気温:{props.otenki.main.temp_min}&deg;C</div>
      </div>
    )
  } else {
    return (
      <div>
        <p>ここにお天気情報を表示します。</p>
      </div>
    )
  }
}

export default AppView

OtenkiViewでは、渡ってききたお天気データを表示しています。もっといろんな情報をもらえるのですが、今回は簡素にしてます。

実行

npm run dev

localhost:8080にwebpack-dev-serverが立ち上がり、ボタンの切り替えで都市ごとの天気を取得することができました。