ふんばりフロントエンジニアのブログ

新米フロントエンジニアの備忘録です。ふんばり温泉タオル欲しい...

React.jsの環境をnpmで作ろう!APIからデータを取得する方法も実践!

 

今回は先延ばしにしていたReact.jsを使うための環境構築、そしてAPIからデータを取得して利用する方法についてご紹介していきたいと思います。

いや〜propsの理解についても苦労しましたが、何と言っても環境構築が一番手間取りましたね...

最近ではDockerなどを使って仮想環境を作成することもあるのですが、どうもその辺りが苦手でして(汗

とは言え、React.jsの場合はnpmで「create-react-app」というコマンドがあるので、それを知ってさえいれば苦労はしなさそうですが...

ということで今回はnpmで一からReactを利用できるようにするまで、そしてタスクランナーの設定までご紹介したいと思います!

さて、Reactの環境を作るために必要なものはなんなのでしょう?

純粋にReactのコードを動かすためなら必要なものは以下の二つです。

npm install react
npm install react-dom

これはreactとreact-domのパッケージを追加するためのコマンドですね。
しかし、これだけではReactのコードを記述することができてもjsxの記法しておりませんので、前回そして前々回に私が作成したようなrender()の中にHTML
を書くような記法は取ることができず、あくまでjsのネイティブ的な書き方をするしかありません。
でも、どうせなら可読性の高いjsxの記法をしたいですよね???(強制)
ということで、今回初めてjsの依存関係を整理してくれる「browserify」を使ってみました。

gulp.task('browserify', function() {
  browserify('./src/index.js', { debug: false })
    .transform(babelify,{presets: ['es2015', 'react']})
    .bundle()
    .on("error", function (err) { console.log("Error : " + err.message); })
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./dist'))
});

私はタスクランナーとしてGulpを使っているのですが、browserifyのタスク指定は上記の通りです。
最終的にindex.jsというコンポーネントをbrowserifyするのですが、このindex.jsは一般的にいちばん親のコンポーネントであるApp.jsのさらに親のコンポーネント
つまり関係性を表すとすれば↓のようになりますね。

index.js
 -App.js
  -Componet1.js

んー、ちょっと具体例がわかりにくいですかね...
本来であればApp.jsでbrowserifyをかけてもいいのですが、なんとなくスッキリしているのが好きなのでindex.jsというさらにラッパーなjsを作成しているような感じです。
ちょっとわかりづらそうなので一部コードを載せてみたいと思います。
index.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './component/App'

ReactDOM.render(
  <App />, document.getElementById('content')
);

Componet.js

mport React from 'react'
import {render} from 'react-dom'

class Question extends React.Component {

  render(){
    if(this.props.count > 0){
      return(
        <div  id="questions" className="questions">
        <button onClick={this.props.countHandler}>push</button>
        <p>{this.props.count}</p>
        {this.props.question.map((data,index)=>{
          if(index === this.props.count){
            return(
              <div key={`Q${index+1}`} id={`q${index+1}`} className="inner">
                <p key={index}>{data.text}</p>
                <img key={`imgQ${index+1}`} src={`../img/q0${index+1}.jpg`} />
                <div className="question">
                  <button className={data.question[0].type} onClick={this.props.answerCountHandler}>{data.question[0].text}</button>
                  <button className={data.question[1].type} onClick={this.props.answerCountHandler}>{data.question[1].text}</button>
              </div>
              </div>
            )
          }
          })
        }
        </div>
      )
    }
    else {
      return (null);
    }
  }
}

export default Question;

App.js

import React from 'react'
import {render} from 'react-dom'
import Question from './question'
import Start from './start'
import Result from './result'

class App extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
      count: 0,
      questions: [],
      results: [],
      countType: [],
      userAnswers: [],
      done: false,
      type: '',
      hasError: false,
      isLoading: false,
    }
  }

//----------関数定義 st----------//

  componentDidMount() {
    this.fetchQuestionData('../data/data.json');
    this.fetchResultData('../data/result.json');
  }

  fetchQuestionData(url) {
  this.setState({ isLoading: true });
  fetch(url)
    .then((response) => {
      if (!response.ok) {
        throw Error(response.statusText);
      }
      this.setState({ isLoading: false });
      return response;
    })
    .then((response) => response.json())
    .then((data) => this.setState({ questions: data }))
    .catch(() => this.setState({ hasErrored: true }));
  }

  fetchResultData(url) {
  this.setState({ isLoading: true });
  fetch(url)
    .then((response) => {
      if (!response.ok) {
        throw Error(response.statusText);
      }
      this.setState({ isLoading: false });
      return response;
    })
    .then((response) => response.json())
    .then((data) => this.setState({ results: data }))
    .catch(() => this.setState({ hasErrored: true }));
  }

//----------関数定義 en----------//


  render() {
    if (this.state.hasError) {
      return <p>error</p>;
    }
    if (this.state.isLoading) {
      return <p>loading . . . </p>;
    }
        return (
        <div className="App">
          <Start count = {this.state.count} countHandler = {this.countHandler.bind(this)}/>

          <Result count = {this.state.count} result = {this.state.results} />

          <Question
           count = {this.state.count}
           question = {this.state.questions}
           countHandler = {this.countHandler.bind(this)}
           answerCountHandler = {this.answerCountHandler.bind(this)}
           />
        </div>
      );
    }
  }

export default App

かなり長くなってしまいましたが関係ないところは一応省きました。
コードの中身はとりあえず追わないでもらって、今みて欲しいところはimportとexportです。
見てもらえればわかると思うのですが、上記の中でindex.js以外は全てそのクラス自体をexportしています。
そしてApp.jsではComponet.jsのクラス「Question」をimportし、index.jsではそのApp.jsをimportしてReactDOMで最終的にimportしてきたApp.jsをrenderしています。
ぶっちゃけindex.jsを作る必要はあまりないのですが、実行クラスを完結にすることでなんとなくスッキリしますよね。
そして最終的にはこのindex.jsをbrowserifyすることで各コンポーネントとの依存関係を解決しています。
いやーbrowserify便利ですね。
さて、Reactの環境を作るのに必要な要点はこのreactのパッケージ、そしてコンポーネントごとに分けるならbrowserifyが必要なのですが、もちろん他にもインストールするべきパッケージは多々あります。
こちらのpackage.jsonをnpm installすればReactの環境は作れると思いますので、ぜひ試してみてください!

{
  "name": "npmreact",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "watch": "watchify -t babelify ./app.js -o ./build.js",
    "start": "browser-sync start --server --files='./*.html, ./*.js'"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "babel-core": "^6.26.3",
    "browser-sync": "^2.24.5",
    "browserify": "^16.2.2",
    "gulp": "^3.9.1",
    "gulp-autoprefixer": "^5.0.0",
    "gulp-babel": "^7.0.1",
    "gulp-sass": "^4.0.1",
    "gulp-sourcemaps": "^2.6.4",
    "gulp-uglify": "^3.0.0",
    "react": "^16.4.1",
    "react-dom": "^16.4.1",
    "vinyl-source-stream": "^2.0.0"
  },
  "devDependencies": {
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babelify": "^8.0.0",
    "watchify": "^3.11.0"
  }
}

さて、Reactの環境構築についてはこのあたりにして、最後にAPIからデータをとって利用する方法についてご紹介します。
今回作成したのはgithubにあるAPIを叩いて、配列のnameだけをリストにしたものです。
codepen.io
ここで重要なのはやはりfetchですね。

Appクラス~~~
  componentDidMount() {
    this.fetchData('https://api.github.com/users/mismith0227/repos')
  }

  fetchData(url) {
    fetch(url)
      .then((response) => response.json())
    .then((responseData) => {
      this.setState({
        data: responseData,
      })
    })
  }

ここではfetchDataというメソッドを定義して、引数にAPIのurlを指定するようにしています。
ComponetDidMount()というのはReactのライフサイクルに関わるメソッドなのですが、これに関してはググってみてください。
さて、肝心のfetchですがデータの取り方や渡し方は普通のjsと同じですのでMDNでfetchを調べてみるといいと思います。
ただ普通とは違うのが最後の「setState()」ですね。
これで空配列のstate「data」にAPI で取得したものをセットしています。
これによってstateとしてAPIのデータを利用できるので、あとは前回と同じです。(map関数に関しては次の機会でお話したいと思います...)
このようにして、APIで叩いた結果をReactで利用できるようになるんですね。
実際に実行できるようになるまでは時間が少しかかってしまいましたが、コード自体はシンプルなのでぜひ上記コードにあるgitのAPIで試してみてください!

さて、今回はReactの環境構築とAPIを叩いてデータを利用する方法を見ていきました。
環境構築を一から行うのはやや骨が折れると思いますが、冒頭でもお話したようにcreate-react-appという便利なコマンドがあるようなので、今ならそこまで時間がかからないのかもしれませんね。
またAPIも簡単に利用できるのでReactでできることの幅も一気に広がると思います。
参考になるかはわかりませんが、私が作成したものを煮るなり焼くなりして動きを掴んでもらえればと思います...
では次回は「Reactでの注意点やmap関数について」をお送りしよう(変わるかも...)と思いますので次回もよろしくお願いいたします!