28
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React.js + ES6 + WebPack チュートリアル

Last updated at Posted at 2016-06-10

React.js + ES6 + WebPack のチュートリアルになります。

私は普段のプロジェクトではAngularJS 1.4 :laughing: を使用しており
ReactJSを知ったかぶり :relieved: でこれまで周りを騙して:sunglasses:きましたが
そろそろ本当にヤバイ感じなので、ちゃんと勉強してみました :stuck_out_tongue:

元ネタのLearnCode.academyさんのビデオはこちら :arrow_down:

本記事では説明のため、かなり簡略化してます。

オリジナルのビデオ
https://www.youtube.com/watch?v=MhkGQAoc7bc
オリジナルのソースコード
https://github.com/learncodeacademy/react-js-tutorials

:tv: 彼のYouTubeチャンネルには、他にも良いビデオがありますので是非ご覧ください。

Chap 1 セットアップ

1. ソースコードの作成

以下のファイルを作成します。

新規作成します。
package.json
webpack.config.js
src/index.html
src/js/client.js
package.json
{
  "name": "react-tutorials",
  "version": "0.0.0",
  "description": "",
  "main": "webpack.config.js",
  "dependencies": {
    "babel-loader": "^6.2.4",
    "babel-plugin-add-module-exports": "^0.1.2",
    "babel-plugin-react-html-attrs": "^2.0.0",
    "babel-plugin-transform-class-properties": "^6.3.13",
    "babel-plugin-transform-decorators-legacy": "^1.3.4",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.5.0",
    "babel-preset-stage-0": "^6.5.0",
    "react": "^15.1.0",
    "react-dom": "^15.1.0",
    "webpack": "^1.13.1",
    "webpack-dev-server": "^1.14.1"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
webpack.config.js
var debug = process.env.NODE_ENV !== "production";
var webpack = require('webpack');
var path = require('path');

module.exports = {
    context: path.join(__dirname, "src"),
    devtool: debug ? "inline-sourcemap" : null,
    entry: "./js/client.js",
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /(node_modules|bower_components)/,
                loader: 'babel-loader',
                query: {
                    presets: ['react', 'es2015', 'stage-0'],
                    plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'],
                }
            }
        ]
    },
    output: {
        path: __dirname + "/src/",
        filename: "client.min.js"
    },
    plugins: debug ? [] : [
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
    ],
};
src/index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>React Tutorials</title>
    <!-- change this up! http://www.bootstrapcdn.com/bootswatch/ -->
    <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.6/cosmo/bootstrap.min.css" type="text/css"
          rel="stylesheet"/>
</head>

<body>
    <div id="app"></div>
    <script src="client.min.js"></script>
</body>
</html>
src/js/client.js
import React from "react";
import ReactDOM from "react-dom";

class Layout extends React.Component {

    render() {
        return (
            <h1>It works!</h1>
        );
    }
}
const app = document.getElementById('app');

ReactDOM.render(<Layout/>, app);

2. セットアップ

$ npm install
$ webpack --watch

Screen Shot 2016-06-10 at 10.36.48 PM.png

src/client.min.jsが生成されます。(このファイルに全てのJSがパッケージされます。少し時間がかかります。)
この時点で下記のフォルダ構成になってるはずです。(node_modules除いてます。)

tree
~/W/myreact1 ❯❯❯ tree -I 'node_modules' .                                                                                     master ✱ ◼
.
├── package.json
├── src
│   ├── client.min.js // WebPackが自動生成します。
│   ├── index.html
│   └── js
│       └── client.js
└── webpack.config.js

2 directories, 5 files

3. 実行

:arrow_forward:ブラウザで index.html を開きます。

$ open index.html

It works!と表示されればOKです。 :thumbsup:

適当にclient.jsを書き換えてみましょう。
WebPackが更新を検出して再コンパイルしてくれます。

WebPackのコンパイルが遅いので、webpack-dev-server使用しましょう。

package.json を以下のように修正します。

package.json
  "scripts": {
    "dev": "webpack-dev-server --content-base src --inline --hot",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

webサーバを起動します。

$ npm run dev

webでアクセスしましょう。
http://localhost:8080/index.html

client.jsファイルを書き換えてみましょう。(勝手に更新してくれます。)
さっきよりかなり速くなります :smile: (差分だけ見てコンパイルしてくれるようです。)

Chap 2 コンポーネント

先ほどのclient.jsLayoutコンポーネントをいじってみましょう。

クラス内にファンクションgetVal()を実装します。
JSXから{this.getVal()}で呼び出します。

client.js
class Layout extends React.Component {

    getVal() { // 実装します。
        return "will";
    }

    render() {
        return (
            <h1>It {this.getVal()} works!</h1> // JSX {...}でJavascriptを書きますハイライトがおかしい。。。 ><
        );
    }
}
const app = document.getElementById('app');

ReactDOM.render(<Layout/>, app);

:arrow_forward: 実行します。It will works! と表示されます。

次は、コンストラクターconstructor()を実装して、this.valを定義します。

client.js
class Layout extends React.Component {

    constructor() { // コンストラクター
        super();
        this.val = "will"; // ここ
    }

    render() {
        return (
            <h1>It {this.val} works!</h1>
        );
    }
}
const app = document.getElementById('app');

ReactDOM.render(<Layout/>, app);

:arrow_forward: 実行します。先ほどと同じ結果になります。

Chap 3 複数コンポーネント

先ほどのLayoutコンポーネントのコードを別ファイルcomponents/Layout.jsに切り出します。

export defaultを付けると、他のファイルからビジブルになります。

components/Layout.js
import React from "react";

export default class Layout extends React.Component {

    constructor() {
        super();
        this.val = "will";
    }

    render() {
        return (
            <h1>It {this.val} works!</h1>
        );
    }
}

client.jsを修正します。
インラインの定義を削除して、代わりにLayoutコンポーネントをインポートします。

client.js
import React from "react";
import ReactDOM from "react-dom";
import Layout from "./components/Layout"; // ここ

const app = document.getElementById('app');

ReactDOM.render(<Layout/>, app);

:arrow_forward: 実行します。変更前と同じ結果になると思います。

次に新たにHeaderコンポーネントを作ります。
新規にcomponents/Header.jsを下記のように作成します。

components/Header.js
import React from "react";

export default class Header extends React.Component {

    render() {
        return (
            <h1>I am Header.</h1>
        );
    }
}

Layout.jsを修正し、Headerコンポーネントをインポートします。
つまり、Layoutが親コンポーネント、Headerが子コンポーネントになります。

components/Layout.js
import React from "react";
import Header from "./Header";

export default class Layout extends React.Component {

    render() {
        return (
            <Header/>
        );
    }
}

:arrow_forward: 実行します。I am Header.と表示されます。

次に、Headerコンポーネントを3つ、リストに入れて表示してみます。

:bulb: returnで返すトップのDOMエレメントは一つである必要があるためdivで囲まないとエラーになります。 :sweat_drops:

components/Layout.js
import React from "react";
import Header from "./Header";

export default class Layout extends React.Component {

    render () {
        var list = [
            <Header/>,
            <Header/>,
            <Header/>
        ];

        return (
            <div>
                {list}
            </div>
        );
    }
}

:arrow_forward: 実行します。(I am Header.が3つ表示されます。)

Chap 4 ステートとプロップ

ステートstateを使用してみましょう。
ステートはコンストラクターで定義します。
その名の通り、コンポーネントの状態を保持する、書き換え可能な値です。
Layout.jsを書き換えます。

Layout.js
import React from "react";

export default class Layout extends React.Component {

    constructor() {
        super();
        this.state = {name1: "Will"}; // ここ
    }

    render () {

        return (
            <div>
                <h1>{this.state.name1}</h1> // 参照します
            </div>
        );
    }
}

:arrow_forward: 実行します。Willと表示されます。(どうでもいいですが、ここではWillは人名ですね。 :sweat_drops: )

次にsetTimeout()を使用して、ステートの値を変更してみましょう。

Layout.js
...
   render () {

        setTimeout(() => {
            this.setState({name1: "Bob"}) // 1秒後にBobに変更
        }, 1000);

        return (
            <div>
                <h1>{this.state.name1}</h1> // リアルなDOM?もちゃんと更新されます
            </div>
        );
    }

1秒後にWillがBobに変わります。

次にプロップを使用してみましょう。

Layoutコンポーネントから、Headerコンポーネントのプロップtitle1インジェクトします。

Layout.js
import React from "react";
import Header from "./Header";

export default class Layout extends React.Component {

    render () {

        const title = "Welcome Will!";
        
        return (
            <div>
                <Header title1={title} /> // インジェクト
            </div>
        );
    }
}

Headerコンポーネントでconsole.log()で確認しましょう。

Header.js
import React from "react";

export default class Header extends React.Component {

    render() {

        console.log(this.props); // 出力: Object {title1: "Welcome Will!"}

        return (
            <h1>I am Header.</h1>
        );
    }
}

:arrow_forward: 実行します。コンソールにObject {title1: "Welcome Will!"}出力されますね。

Chap 5 イベント

Headerコンポーネントにテキストフィールドを追加します。

やりたいことは、テキストフィールドにタイプした文字列を、リアルタイムに上の部分に表示する よくあるやつですね。

まずは手始めに仕組みづくりをします。

Header.jsを以下のように修正します。

Header.js
import React from "react";

export default class Header extends React.Component {

    render() {

        return (
            <div>
                <h1>{this.props.title1}</h1> //テキストフィールドの値を動的にここに表示したい
                <input />
            </div>               
        );
    }
}

Layout.jsを以下のように修正します。

Layout.js
import React from "react";
import Header from "./Header";

export default class Layout extends React.Component {

    constructor() {
        super();
        this.state = {
            theTitle: "Welcome", // コンポーネントの状態を保たせます。
        };
    }

    render () {

        setTimeout( () => {
            this.setState({theTitle: "Welcome Will!"}); // 3秒後に、状態を変化させます。
        }, 3000);
        
        return (
            <div>
                <Header title1={this.state.theTitle} /> // Headerコンポーネントにインジェクトします
            </div>
        );
    }
}

:arrow_forward: 実行します。3秒後に、表示が変わりましたでしょうか?
もちろん、この段階ではテキストフィールドに値を入力しても何も変わりません。 :worried:

完成形

  1. 親のLayoutコンポーネントに、状態theTitleを変更するインターフェースchangeTitleInterface(title)を実装します。
  2. Headerコンポーネントのプロップcallback1に、インターフェースを渡します。子コンポーネントHeaderにコールバックさせるわけです。
    (bindでthisを親コンポーネントに束縛します。)
Layout.js
import React from "react";
import Header from "./Header";

export default class Layout extends React.Component {

    constructor() {
        super();
        this.state = {
            theTitle: "Welcome",
        };
    }

    changeTitleInterface(title) {
        this.setState({theTitle: title})
    }

    render () {
        return (
            <div>
                <Header title1={this.state.theTitle} callback1={this.changeTitleInterface.bind(this)} />
            </div>
        );
    }
}

Headerコンポーネントを下記のように修正します。

Header.js
import React from "react";

export default class Header extends React.Component {

    handleChange(e) {
        const title = e.target.value;
        this.props.callback1(title); // 親のインターフェースをコールバック
    }

    render() {

        return (
            <div>
                <h1>{this.props.title1}</h1>
                <input onChange={this.handleChange.bind(this)} /> // ココは普通にonChangeイベントをフックします
            </div>               
        );
    }
}

onChangeイベントをフックして、親のインターフェースをコールバックします。
そのさい、テキストフィールドの値を渡してあげるわけです。

すると、

  1. 親の状態theTitleが更新される
  2. Headerのプロップthis.props.title1も変わる

で、めでたく表示も変わるわけです。

状態自体はあくまで、親コンポーネントが管理し、子供はステートレスな作りにしておきます。

Chap 6 ルーティング

ルーティングを実装してみましょう。

まず、react-routerhistoryをインストールします。

$ npm -S install react-router
$ npm -S install history@1

React Routerを使用して、タブバーを実装してみましょう。
3つのリンクを配置し、クリックしたリンクに応じてページの一部を更新します。

親ページとなるLayoutコンポーネントと、子ページとなるPage1,Page2,Page3,コンポーネントを作成します。

client.jsを下記のように修正します。

client.js
import React from "react";
import ReactDOM from "react-dom";
import {Router, Route, IndexRoute, hashHistory} from "react-router"

import Layout from "./components/Layout"; // 親ページ
import Page1 from "./components/Page1"; // 子ページを3つインポート
import Page2 from "./components/Page2";
import Page3 from "./components/Page3";

const app = document.getElementById('app');

ReactDOM.render(
    <Router history={hashHistory}>
        <Route path="/" component={Layout}> // テンプレートとなる親ページ
            <IndexRoute component={Page1}></IndexRoute> // 子ページ1デフォルトページ
            <Route path="page2" component={Page2}></Route> // 子ページ2
            <Route path="page3" component={Page3}></Route> // 子ページ3
        </Route>
    </Router>,
app);

import {XXX} from "YYY"

export defaultdefaultが付いていないメンバをインポートする場合は、カーリーで囲みます。

/page2にアクセスすると、Page2コンポーネントがLayoutコンポーネントに読み込まれます。

親ページとなるLayoutコンポーネントを下記のように修正します。
this.props.childrenに、選択したPageコンポーネントがセットされます。

components/Layout.js
import React from "react";
import {Link} from "react-router" // リンクを作成するコンポーネント

export default class Layout extends React.Component {

    render () {
        return (
            <div>
                <h1>Layout</h1>

                {this.props.children} // ここに選択した子コンポーネントが読み込まれます。

                <Link to="/">Show Page1</Link> // リンクを3つ作成します
                <Link to="page2">Show Page2</Link>
                <Link to="page3">Show Page3</Link>
            </div>                
        );
    }
}

最後に子ページのファイル3つを作成します。

components/Page1.js
import React from "react";

export default class Page1 extends React.Component {

    render () {
        return (
            <h1>Page1</h1> // 同様にPage2, Page3を作成します
        );
    }
}

:arrow_forward: 実行します

Screen Shot 2016-06-11 at 4.52.04 PM.png

URLを見ると、SPAの特徴であるハッシュ#でルーティングしているのがわかります。

Flux編へ

28
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?