兵どもが、夢のあとさき

JavaScript 系の言語に興味を惹かれ、まったりと更新しております

Reactって何だー(ソフトウェア開発編 その2)

photo at Moiwa mountain

 

 おはようございます。暑い日が続いてますね。皆さま熱中症には十分注意してください。それでは早速続きと参りましょう。

Axiosの使い方

 前ページで、Axios の使い方が間違っていると記載しましたが、本来の Axios は以下の様な記述をする必要があります(今回は get メソッド)。

 

axios.get(URLを記述)
 .then(function(response) {
        // getが成功した時の処理
        console.log(response)
    })
   .catch(function(error) {
       // getが失敗した時の処理
       console.log(error)
    })
    .finally(function() {
      // axiosの処理結果によらずいつも実行させたい処理を記述
    })

 

 それでは実際に実装してみましょう。そして成功時、失敗時の時の処理の動きを確認しましょう。

15~26行目を書き換えました。実行してみると、

 以前同様、宗谷地方の気象情報を得ることができました。それでは、次はエラーを発生させてみましょう。URL を書き換えます。

 存在しないエリア番号を指定しましょう。結果は、

 エラーコード 404 (Not Found)が出力されています。つまり存在しないファイルということですね。これをデバッガではなく UI 上に出力させるためコードを下記の様に書き換えました。

 
import logo from './logo.svg';
import './App.css';
import axios from "axios";  // npm install axios を実施済

function App() {

  const weatherData = 'みんなの天気予報'
  var obj = ''

  const Req = async () => {

    // const area = "011000"   // 宗谷地方
    const area = "000000"   // 何処?

    await axios.get(url + area + ".json")
      .then(function(response) {
        // get処理の成功時
        // console.log(response)
        obj = response.data[0].timeSeries[0].areas[0].area.name
        + ": "
        + response.data[0].timeSeries[0].areas[0].weathers[0];

      // alert(obj)
      document.getElementById('weather').textContent = obj

      })
      .catch(function(error) {
        // 処理の失敗
        console.log(error)
        if(error.response) {
          document.getElementById('weather').textContent = 'エラーが発生しました'
          + ': ' + error.config.url
          + ': ' + error.response.status
        }
      })
      .finally(function() {
        // 成功時も、失敗時も実行される
      })
 
    }

  const buttonAlert = () => {
    // alert(JSON.stringify({weatherData})); // コメントアウトしました
    Req()
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div id='weather'>
          <p>{weatherData}</p>
        </div>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
        </a>
        <div><button onClick={buttonAlert}> {weatherData} </button></div>
      </header>
    </div>
  );
}

export default App

 これでエラーステータス 404 が表示されるかと実行すると、

 エラーステータスが、0 となってしまいます。これでは、エラーハンドリングを行うことができません(デバッガでも、error.response.status は、0 でした)。エラーハンドリングは非常に重要性が高く、トラブルが発生した際にエンドユーザに現在何が発生しているのかを通知(回線障害、サーバ障害、不正処理等々)するために必要なものです。従って、ここの部分(エラーハンドリング)で手を抜くと運用に入った際にトラブルシューティングに非常に時間を要する可能性があります。

 しかし、困ったな… 悩んでいても仕方がないのでここは一度ペンディングします。取得方法が判明したら改めてご報告したいと思います。

 

コンポーネント化について

 これまで App.js にすべてのソースを書いてきました。小さなプログラムですからこれで問題はないのですが、様々な機能を一つのファイルにすべて詰め込むとバグが出た際にデバッグが困難になったり、先だっても述べましたがフッタの様に複数のプログラムで同様の機能を各々作成していてはメンテナンス性も悪くなります。

 React は元々コンポーネント化を考慮された言語であり、部品と呼ばれるコンポーネントを組み合わせることにより、より簡易な構築そしてメンテナンス性の高いプログラムが書けるようになっています。それでは、App.js をコンポーネント化していきます。

import logo from './logo.svg';
import './App.css';
import axios from "axios";  // npm install axios を実施済

function App() {

  const weatherData = 'みんなの天気予報'
  var obj = ''

  const Req = async () => {

    const area = "011000"   // 宗谷地方
    // const area = "000000"   // 何処?

    await axios.get(url + area + ".json")
      .then(function(response) {
        // get処理の成功時
        // console.log(response)
        obj = response.data[0].timeSeries[0].areas[0].area.name
        + ": "
        + response.data[0].timeSeries[0].areas[0].weathers[0];

      // alert(obj)
      document.getElementById('weather').textContent = obj

      })
      .catch(function(error) {
        // 処理の失敗
        console.log(error)
        if(error.response) {
          document.getElementById('weather').textContent = 'エラーが発生しました'
          + ': ' + error.config.url
          + ': ' + error.response.status
        }
      })
      .finally(function() {
        // 成功時も、失敗時も実行される
      })
 
    }

  const buttonAlert = () => {
    // alert(JSON.stringify({weatherData})); // コメントアウトしました
    Req()
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div id='weather'>
          <p>{weatherData}</p>
        </div>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
        </a>
        <div><button onClick={buttonAlert}> {weatherData} </button></div>
      </header>
    </div>
  );
}

export default App

 現在の App.js プログラムです。この中で、Req 関数を呼び出しています。この部分をコンポーネント化してみます。

 まず Req 関数の部分を選択し、切り取ります(ctrl + x)。
 次に、下向き鏃の src を選択し、「新しいファイルの作成」(ctrl + n)を実行。

 この画面で「言語の選択」をクリック。「言語モードの選択」に react と入力します。

 JavaScript React をクリック。

 先ほどのソースをペースト(ctrl + v)

 「ファイル(F)」から、「名前を付けて保存」を選択し、Req と名称をつけて保存してください。

 src フォルダのやや下の方に、Req.jsx*1 というファイルが生成されました。
 生成されたファイルの内容は以下の通りです。(不要なコメントを外したり、obj 変数を関数内に入れてあります)

const Req = async () => {

  const area = "011000"   // 宗谷地方
  var obj = ''

  await axios.get(url + area + ".json")
    .then(function(response) {
      // get処理の成功時
      // console.log(response)
      obj = response.data[0].timeSeries[0].areas[0].area.name
      + ": "
      + response.data[0].timeSeries[0].areas[0].weathers[0];

    document.getElementById('weather').textContent = obj

    })
    .catch(function(error) {
      // 処理の失敗
      console.log(error)
      if(error.response) {
        document.getElementById('weather').textContent = 'エラーが発生しました'
        + ': ' + error.config.url
        + ': ' + error.response.status
      }
    })
    .finally(function() {
      // 成功時も、失敗時も実行される
    })
  }

 そして、元の App.js からは Req() に相当する部分を削除して下さい。ソースは下記となります。

import logo from './logo.svg';
import './App.css';
import axios from "axios";  // npm install axios を実施済

function App() {

  const weatherData = 'みんなの天気予報'
  const buttonAlert = () => {
    // alert(JSON.stringify({weatherData})); // コメントアウトしました
    Req()
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div id='weather'>
          <p>{weatherData}</p>
        </div>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
        </a>
        <div><button onClick={buttonAlert}> {weatherData} </button></div>
      </header>
    </div>
  );
}

export default App

 

 さて結果は?

 エラーとなりました。Req() 関数が見つからないようです。

 実はコンポーネントは明示的に自分自身を外へ開示する必要があります。そして読み込む方も、そのコンポーネントを明示的に読み込む必要があります。具体的には、

Req.jsx には

export default Req

と記述する必要があり、読み込む側の App.js には

import Req from './Req';

を記述する必要があります。export は最終行に、import は頭に記述することになっています(単なる慣例ですけど)。

 もう一つ大事な点があります。Axios ライブラリの読み込みに関してです。Req() 関数をコンポーネント化しましたので、Axios は App.js ではなく Req.jsx で読み込む必要があります。そこで、App.js の

 
import axios from "axios";

を削除して、Req.jsx の頭に張り付けてください。

 実行します。と言うか React は勝手にコンパイルされるのでエラーが出ていた場合には、本章を再度読み直して確認してください。

 この画面になっていればOKです。ボタンをクリックすると、

 はい、元の挙動どおりに稼働しました。これがコンポーネント化です。

 

引数の引き渡し

 一応コンポーネント化は行われましたが、これでは何がおいしいのかメリットが見当たりません。そこで次は、Req コンポーネントに引数を渡して(そうですね、宗谷地方以外の地域も表示できるようにして)みましょう。引数を渡すことによって、Req コンポーネントの使いまわしが可能となります。

 まず、親コンポーネントである App.js を下記の様にReq メソッドに引数を指定します。

function App() {

  const weatherData = 'みんなの天気予報'
  var area = ''
  var areaName = ''

  const buttonAlert = () => {
    Req(area = '011000', areaName = '北海道')
  }
 
 

 次に、Req.jsx メソッドが引数を受け取る様に書き換えます。

const Req = async (area, areaName) => {


  await axios.get(url + area + ".json")
    .then(function(response) {
      // get処理の成功時
      let obj = areaName + "の" + response.data[0].timeSeries[0].areas[0].area.name
      + ": "
      + response.data[0].timeSeries[0].areas[0].weathers[0];

        document.getElementById('weather').textContent = obj
    })

 結果は、

 この様に引数が引き渡されました。

 これを複数の地域の情報を表示させます。App.js を次のように書き換えてみましょう。(変更部分のみ)

App.js

  const buttonAlert = () => {
    Req(area = '471000', areaName = '沖縄地方')
    Req(area = '350000', areaName = '山口県')
    Req(area = '330000', areaName = '中国地方')
    Req(area = '270000', areaName = '大阪府')
    Req(area = '230000', areaName = '中部地方')
    Req(area = '130000', areaName = '東京都')
    Req(area = '020000', areaName = '青森県')
    Req(area = '016000', areaName = '札幌')
    Req(area = '012000', areaName = '北海道中部')
  }

Req.jsx

  await axios.get(url + area + ".json")
    .then(function(response) {
      // get処理の成功時
      let obj = areaName + "の" + response.data[0].timeSeries[0].areas[0].area.name
        + ": "
        + response.data[0].timeSeries[0].areas[0].weathers[0];

        // document.getElementById('weather').textContent = obj

        var c = document.getElementById('weather');
        c.insertAdjacentHTML('afterbegin', "<li align='left'>" + obj + "</li>");

    })

 結果は、

の様になりました。
 ただ、Req.jsx を複数呼び出しているのはみっともないので連想配列を使って、シンプル化します。
 App.js

 この様に、連想配列を使用することにより、ソースの見通しが良くなりました。コメント部分は不要なので削除してしまいましょう。

 App.js

import './App.css';
import Req from './Req';

function App() {

  const weatherData = 'みんなの天気予報'

  let areaData = {'012000': '北海道中部', '016000': '札幌', '020000': '青森県', '070000': '福島県',
                  '130000': '首都', '230000': '中部地方', '270000': '近畿地方', '330000': '中国地方',  
                  '350000': '山口県', '471000': '沖縄地方',
    }

  const buttonAlert = () => {    
   
    Object.keys(areaData).forEach(key => {
      Req(key, areaData[key])
    })
  }

  return (
    <div className="App">
      <header className="App-header">
        <div id='weather'>
          <p>{weatherData}</p>
        </div>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
        </a>
        <div><button onClick={buttonAlert}> {weatherData} </button></div>
      </header>
    </div>
  );
}

export default App

 Req.jsx

import axios from "axios";

const Req = async (area, areaName) => {


  await axios.get(url + area + ".json")
    .then(function(response) {
      // get処理の成功時
      let obj = areaName + ": " + response.data[0].timeSeries[0].areas[0].area.name
        + ": "
        + response.data[0].timeSeries[0].areas[0].weathers[0];

        var c = document.getElementById('weather');
        c.insertAdjacentHTML('beforeend', "<li align='left'>" + obj + "</li>");
      })

    .catch(function(error) {
      // 処理の失敗
      console.log(error)
      if(error.response) {
        document.getElementById('weather').textContent = 'エラーが発生しました'
        + ': ' + error.config.url
        + ': ' + error.response.status
      }
    })

    .finally(function() {
      // 成功時も、失敗時も実行される
    })
}

export default Req

 結果は?

 結果は表示されましたが、地方の順序がばらばらです。やはり、北から or 南から表示させたいですね。連想配列は通常の配列と異なり、順不同で格納されています。そのため、取り出す際に登録した順には得ることができないのです。

 連想配列で順番を保ちたい場合には Map オブジェクトを使用するとうまくいきます。

App.js

import './App.css';
import Req from './Req';

function App() {

  const weatherData = 'みんなの天気予報'

  const buttonAlert = () => {
   
    const areaData = new Map([
      ['012000', '北海道中部'], ['016000', '札幌']    , ['020000', '青森県'],
      ['070000', '福島県']    , ['130000', '首都']    , ['230000', '愛知県'],
      ['270000', '近畿地方']  , ['280000', '兵庫県']  , ['330000', '中国地方'],
      ['350000', '山口県']    , ['471000', '沖縄地方'],
    ])
 
    Req(areaData)

 }

  return (
    <div className="App">
      <header className="App-header">
        <div id='weather'></div>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
        </a>
        <div><button onClick={buttonAlert}> {weatherData} </button></div>
      </header>
    </div>
  );
}

export default App

 連想配列を Map で定義し、そのオブジェクトをまるごと Req.jsx に引数として渡します。

Req.jsx

import axios from "axios";

const Req = async (areaData) => {

  for (let [key, areaName] of areaData) {
 
    const obj = url + key + ".json"

    await axios.get(obj)

      .then(function(response) {
      // get処理の成功時
      let obj = areaName + ": " + response.data[0].timeSeries[0].areas[0].area.name
        + ": "
        + response.data[0].timeSeries[0].areas[0].weathers[0];

        let pattern = / /ug
        obj = obj.replace(pattern, '')

        let c = document.getElementById('weather');
        c.insertAdjacentHTML('afterend', "<li align='left'>" + obj + "</li>");
      })

      .catch(function(error) {
      // 処理の失敗
        console.log(error)
        if(error.response) {
          document.getElementById('weather').textContent = 'エラーが発生しました'
          + ': ' + error.config.url
          + ': ' + error.response.status
        }
      })

      .finally(function() {
        // 成功時も、失敗時も実行される
      })
    }
}

export default Req

 あとは、Req.jsx で、そのオブジェクトを受け取り、for で各要素を処理していきます。ついでに、正規表現を使用して

 let pattern = / /ug
 obj = obj.replace(pattern, '')

で余計な全角スペースを削っています。さて結果は、

 見事に、南から北への天気予報が表示されました。余計なスペースも削られて、見やすくなっていますね。
 この章はここまでです。次章では、React の肝である JSX と props を使用したコンポーネントを作成していきたいと思います。

 

 

 

 

 

*2

 デフォルトでは、React DOM は JSX に埋め込まれた値をレンダー前にエスケープします。このため、自分のアプリケーションで明示的に書かれたものではないあらゆるコードは、注入できないことが保証されます。レンダーの前に全てが文字列に変換されます。これは XSS (cross-site-scripting) 攻撃の防止に役立ちます。

 

JSX の導入 – React より引用。これにより、同時にクロスサイトリクエストフォージェリ (CSRF) を防ぐことも可能となります。

 
 

MongoDB などの NoSQL データベースでは JSON と同様のフォーマットのデータをそのまま格納することができます。

*1:Reactは、JavaScriptと明確に異なることを明示するために、.jsx という拡張子を付けることが慣例となっています

*2:Axiosのセキュリティについて

*3:今後使いたいDBについて