目次
- 今から始めるReact入門 〜 React の基本 ←★ここ
- 今から始めるReact入門 〜 React Router 編
- 今から始めるReact入門 〜 flux編
- 今から始めるReact入門 〜 Redux 編: immutability とは
- 今から始めるReact入門 〜 Redux 編: Redux 単体で状態管理をしっかり理解する
- 今から始めるReact入門 〜 Redux 編: Redux アプリケーションを作成する
- 今から始めるReact入門 〜 Mobx 編
React をはじめるにあたって
普段バックエンドを触っている人間として、フロントエンド側の知識も触れておこうということで個人的なReact の勉強記を残しておきたいと思います。
すでにReact の記事は多く出回っていますが、ここでは最近のReact 及びReact Router, Redux, その他Redux のミドルウェア、MobX について触っていき、その他webpack v4, babel も組み合わせてReact の仕事現場でも通用するレベルを想定して書いていきたいと思います。
今回React の勉強で主に参考にしたのは以下のReact JS Tutorials の動画です。
- React JS Tutorials
途中webpack のバージョンが古くて動かない部分があったりしますが、ここではそれらも対応するように書き換えて対応していきます。
また長くなりますので、複数の部に分けて説明をしてきたいと思います。
本レクチャで使用するファイル
本レクチャで使用するファイルは以下のリポジトリにあります。
React とは
一言でいうとView レイヤのJavaScript フロントエンドフレームワークで、以下のような特徴があります。
宣言的
アプリケーションで各状態に対するシンプルな設計をすることで、React はデータが変更された時に、適切なコンポーネントだけを効率的に更新してレンダリングすることができます。
開発者は更新すべき適切なコンポーネントを見つけるロジックを組んだりといったことに注視する必要がありません。
宣言的なview は予測性を高め、コードのデバッグしやすくしてくれます。
また、JSX 記法(JavaScript 内にHTML タグを注入するような記法)を利用でき、どういう形でレンダリングしてほしいかといったことが想像しやすくなっています。
コンポーネントベース
自身の状態を管理するカプセル化されたコンポーネントを構築することで、それらのコンポーネントを呼び出し複雑なUI もコンポーネント呼び出しで作成できるようになります。
それらコンポーネントはテンプレートではなくJavaScript で記述されることで、豊富なデータを簡単にアプリケーションに渡すことができ、DOM の外側で状態を保持することができるようになります。
使い回し
React は開発者の他の技術についての知識を仮定していないため、既存のコードの書き換え無しにReact で新しい機能を開発することもできます。
例えばNode を使用してSSR(サーバサイドレンダリング) に対応させたりReact Native を使用して強力なモバイルアプリケーションも開発することができます。
またこれらの特徴以外に、React はFacebook をバックエンドに持っており、かつてはBSD + 特許といったライセンス形態でエンタープライズアプリケーションの開発に二の足を踏んでしまう人も居たかもしれませんが、2017年9月頃にはMIT ライセンスに改定され以前にも増して使いやすいものとなっています。
それではこういったReact の簡単な特徴を掴んだところで、次は早速React をつかってプログラミングを進めていきましょう。
環境の準備
今回は以下のような環境で作成していきました。
- 環境概要
演算子 | 意味 |
---|---|
node version | v8 系 |
npm version | 5 系 |
webpack | 4 系 |
OS | Arch Linux |
今回はまずReact の基本的な動作を確認するために、シンプルな構成を例にReact を体験していきましょう。
ワークスペースの作成
React の基本動作を実感するアプリケーション制作のための作業ディレクトリreact-tutorial
を作成します。
$ mkdir react-tutorial
$ cd react-tutorial
$ mkdir -p src/js
npm init
でプロジェクトを作成します。
$ npm init
......
package name: (react-tutorial)
version: (1.0.0)
description:
entry point: (index.js) webpack.config.js # <- "webpack.config.js" 入力(先にwebpack.config.js 作っておけばデフォルトで選択される)
test command:
git repository:
keywords:
author: Your Name
license: (ISC)
......
補足: @ksilverwall さんのコメントより、description:
, git repository:
に何も入力しないでプロジェクトを作成すると、後のnpm install
コマンド等を実行した時に以下のような警告が出ます。
npm WARN react-tutorial@1.0.0 No description
npm WARN react-tutorial@1.0.0 No repository field.
この警告自体に問題はないのでそのまま進めて大丈夫です。が、気になる人はdescription:
, git repository:
に適当な値を入れるようにしてください。
次にreact, webpack, babel をインストールします。
実際のプロジェクでの作業を想定してbabel でES6 書式に対応しながらwebpack でモジュールバンドリングしながら開発していきたいと思います。
$ npm install --save-dev webpack webpack-cli webpack-dev-server
$ npm install -g webpack webpack-cli
$ npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader
$ npm install --save-dev react react-dom
webpack.config.js
ファイルを作成し、バンドリングルールを書いていきます。
下記のようにwebpack.config.js
を設定することで./src/js/client.js
を起点にファイル内部のimport
構文を読み取りそれらソースコードをモジュールバンドリングしていくようになります。
var debug = process.env.NODE_ENV !== "production";
var webpack = require('webpack');
var path = require('path');
module.exports = {
context: path.join(__dirname, "src"),
entry: "./js/client.js",
module: {
rules: [{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
use: [{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-react', '@babel/preset-env']
}
}]
}]
},
output: {
path: __dirname + "/src/",
filename: "client.min.js"
},
plugins: debug ? [] : [
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
]
};
次にsrc/index.html
ファイルを作成します。
src/index.html
ではwebpack により作成されたclient.min.js をロードするように指定します。
<!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>
client.js を開きReact のJSX を使い画面にWelcome!
とレンダリングしてみましょう。
import React from "react";
import ReactDOM from "react-dom";
class Layout extends React.Component {
render() {
return (
<h1>Welcome!</h1>
);
}
}
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
これで準備は完了です。
webpack コマンドでclient.min.js ファイルを作成し、index.html ファイルをChrome で開いてみましょう。
$ webpack --mode development
Hash: f97a4ff0cc28741ce49d
Version: webpack 4.6.0
Time: 995ms
Built at: 2018-04-29 11:18:20
Asset Size Chunks Chunk Names
client.min.js 1.66 MiB main [emitted] main
Entrypoint main = client.min.js
[./js/client.js] 2.31 KiB {main} [built]
+ 21 hidden modules
$ google-chrome-stable ./src/index.html
Welcome!
が表示されます。初めてのJSX を使い要素をレンダリングしました。
client.js の内容を書き換えることにより表示が変わることも確認してみましょう。
import React from "react";
import ReactDOM from "react-dom";
class Layout extends React.Component {
render() {
return (
- <h1>Welcome!</h1>
+ <h1>It works!</h1>
);
}
}
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
買い替えたらwebpack コマンドでトランスパイルして、もう一度Chrome で画面を開いてみましょう。
$ webpack --mode development
......
$ google-chrome-stable ./src/index.html
今度はIt works!
と表示されます。
このようにReact ではJSX 記法を利用して画面をレンダリングすることができるようになっています。
webpack-dev-server で開発用web サーバを起動する
webpack には開発用のweb サーバも用意されています。
開発用のweb サーバ(webpack-dev-server)を使うことでバンドリングと開発中のサービスの公開を同時に行うことができ、開発中の作業効率を一段と引き上げますので覚えて損は無い機能です。
それでは試しにwebpack-dev-server を起動してみましょう。
$ ./node_modules/.bin/webpack-dev-server --content-base src --mode development
webpack 4系を使っている場合は
$ webpack serve
に代えて実行してください。
この状態でhttp://localhost:8080
へWeb ブラウザでアクセスしてみましょう。
すると先程表示されたIt works!
が表示されるはずです。
この状態で./src/js/client.js
を書き換えて上書き保存してみましょう。
import React from "react";
import ReactDOM from "react-dom";
class Layout extends React.Component {
render() {
return (
- <h1>It works!</h1>
+ <h1>Welcome!</h1>
);
}
}
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
そうするとweb 画面の方もリアルタイムで表示が変わったことが確認できたでしょうか?
開発中はこのようにwebpack-dev-server を使って効率よく開発を進めていきましょう。
npm スクリプトにwebpack-dev-server 起動コマンドを登録
webpack-dev-server コマンドをnpm スクリプトとしてpackage.json に登録し、npm start
コマンドを使用してこのwebpack-dev-server が起動するように設定しましょう。
npm スクリプトでは./node_module ディレクトリ下に自動的にPATH が通るようになるのでpackage.json には以下のように記述すればOK です。
......
"scripts": {
+ "start": "webpack-dev-server --content-base src --mode development --inline",
"test": "echo \"Error: no test specified\" && exit 1"
},
......
追記が終わったら次のようにしてwebpack-dev-server を起動してみましょう。
$ npm start
この状態で先ほどと同様にWeb ブラウザでhttp://localhost:8080
へアクセスするとReact によってレンダリングされた画面が確認できるようになっていると思います。
以上で開発環境の準備ができました。
次からは実際のReact の開発現場を想定したWeb アプリケーションを開発していきます。
JSX
webpack、babel を使って簡単なReact アプリケーションプロジェクトを作成しました。
次はReact アプリケーションのプログラム中に出てきたJSX について説明していきます。
JSX について
React アプリケーションを作成すると以下のような記述が出てきました。
......
render() {
return (
<h1>It works!</h1>
);
}
......
この<h1>...</h1>
という記載はHTML タグで、本来これはJavaScript の文法としては見かけない書き方です。
しかしこのような書き方をすることで、実際にHTML タグのh1 タグの内容が画面にレンダリングされました。
これはwebpack コマンド実行時に内部的にbabel が呼ばれて、以下のように変換されることにより一般的なWeb ブラウザでも解釈できるJavaScript 構文へと変換されているからです。
......
return React.createElement(
"h1",
null,
"Welcome!"
);
......
React.createElement メソッドが呼び出され、そこにh1
というHTML タグ名と、タグの属性値(今回はnull)、タグに挟まれるテキストWelcome!
が引数として渡されます。
実際はbabel 単体で走らせると、これよりは少し複雑な形式へとコンパイルされます。
$ npm install -g --no-save @babel/cli
$ npm install -g --no-save @babel/core @babel/preset-react @babel/preset-env
$ babel --presets @babel/preset-react,@babel/preset-env ./src/js/client.js
......
_createClass(Layout, [{
key: "render",
value: function render() {
return _react2.default.createElement(
"h1",
null,
"It works!"
);
}
}]);
......
ただ、いずれにせよJavaScript の構文として正しい構文のものへと変換されます。
このようにwebpack とbabel を使うことによってReact/JSX の構文は典型的なJavaScript へとコンパイルされるため、あらゆるWeb ブラウザ上でも実行可能となるのです。
複数コンポーネントを記載する場合の注意点
JSX を使うと普通にHTML を書く感覚でJavaScript 内にタグを埋めていくことができるのですが、注意点があります。
src/js/client.js
を以下のように書き換えてみましょう。
import React from "react";
import ReactDOM from "react-dom";
class Layout extends React.Component {
render() {
return (
<h1>Welcome!</h1>
+ <h1>It's works!</h1>
);
}
}
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
上記のような書き方は許可されていません。
Google Chrome のF12 キーを押下してConsole を表示するとエラーメッセージの中に以下のようなメッセージが確認できると思います。
SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag
JSX のコンポーネントをそのまま2 つ並べることはできず、上位のコンポーネントでwrap するようにしなければなりません。
次のように記述することでエラーを正常にレンダリングすることができるようになります。
import React from "react";
import ReactDOM from "react-dom";
class Layout extends React.Component {
render() {
return (
- <h1>Welcome!</h1>
- <h1>It's works!</h1>
+ <div>
+ <h1>Welcome!</h1>
+ <h1>It's works!</h1>
+ </div>
);
}
}
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
変数の取扱について
JSX 内にJavaScript で定義した変数を埋め込むには{変数名}
と記述して埋め込むことができます。
import React from "react";
import ReactDOM from "react-dom";
class Layout extends React.Component {
render() {
let name = "Tsutomu";
return (
- <div>
- <h1>Welcome!</h1>
- <h1>It's works!</h1>
- </div>
+ <h1>It's {name}!</h1>
);
}
}
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
また{数式}
とすることで数式の計算結果を組み込むこともできます。
import React from "react";
import ReactDOM from "react-dom";
class Layout extends React.Component {
render() {
return (
- <h1>It's {name}!</h1>
+ <h1>It's {1 + 2}!</h1>
);
}
}
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
また、変数の代わりに関数を直接組み込むこともできます。
import React from "react";
import ReactDOM from "react-dom";
class Layout extends React.Component {
render() {
return (
- <h1>It's {1 + 2}!</h1>
+ <h1>It's {this.get_result(3)}!</h1>
);
}
+ get_result(num) {
+ return 1 + num;
+ }
}
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
また、匿名関数を作成して即時呼び出しをすることもできます。
import React from "react";
import ReactDOM from "react-dom";
class Layout extends React.Component {
render() {
return (
- <h1>It's {this.get_result(3)}!</h1>
+ <h1>It's { ((num) => { return 1 + num; })(3) }!</h1>
);
}
- get_result(num) {
- return 1 + num;
- }
}
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
また、constructor を使用してその中で初期化されたメンバ変数を参照することも可能です。
import React from "react";
import ReactDOM from "react-dom";
class Layout extends React.Component {
+ constructor() {
+ super();
+ this.name = "Tsutomu";
+ }
render() {
return (
- <h1>It's { ((num) => { return 1 + num; })(3) }!</h1>
+ <h1>It's {this.name}!</h1>
);
}
}
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
React のコンポーネント化
React はコンポーネント化することで、前回記事のように一つのファイルに全てのコンテンツを含めてしまうのではなく、幾つかのファイルに分けていき再利用性を高めることができます。
import React from "react";
import ReactDOM from "react-dom";
class Layout extends React.Component {
constructor() {
super();
this.name = "Tsutomu";
}
render() {
return (
<h1>It's {this.name}!</h1>
);
}
}
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
このソースコードの中のLayout クラスを新しいファイルsrc/js/components/Layout.js
に移植します。
$ mkdir -p ./src/js/components
$ touch src/js/components/Layout.js
src/js/components/Layout.js
ファイルを作成し以下の内容を記述をsrc/js/client.js
から移動します。
また、これは後ほどsrc/js/client.js
から参照できるようにexport 構文を使って外部からアクセスできるようにしておきます。
import React from "react";
export default class Layout extends React.Component {
constructor() {
super();
this.name = "Tsutomu";
}
render() {
return (
<h1>It's {this.name}!</h1>
);
}
}
client.js にこのLayout コンポーネントを取り込んでいきます。
import React from "react";
import ReactDOM from "react-dom";
+import Layout from "./components/Layout";
-class Layout extends React.Component {
- constructor() {
- super();
- this.name = "Tsutomu";
- }
- render() {
- return (
- <h1>It's {this.name}!</h1>
- );
- }
-}
-
const app = document.getElementById('app');
ReactDOM.render(<Layout/>, app);
このようにすることでJSX のLayout タグが今までどおりclient.js でも利用できるようになります。
npm start
コマンドを実行しhttp://localhost:8080
で先ほどと変わらずにIt's Tsutomu!
が表示されることを確認しましょう。
Header, Footer コンポーネントの作成
Layout コンポーネントを作成したら次はHeader, Footer コンポーネントも作成していきましょう。
Header コンポーネントの作成
src/js/components/Header.js
ファイルを作成し、Header コンポーネントを作っていきます。
import React from "react";
export default class Header extends React.Component {
render() {
return (
<header>header</header>
);
}
}
Header コンポーネントを作成したら、Layout.js にそれを取り入れていきます。
import React from "react";
+import Header from "./Header";
export default class Layout extends React.Component {
- constructor() {
- super();
- this.name = "Tsutomu";
- }
render() {
return (
- <h1>It's {this.name}!</h1>
+ <div>
+ <Header />
+ </div>
);
}
}
ここで一旦http://localhost:8080
をWeb ブラウザで表示して確認します。Header.js でコンポーネント化しても先ほどと同じ内容が表示されていることが確認できると思います。
正常にheader が表示されたでしょうか?
次はLayout.js 内のHeader コンポーネントを3 つに増やして再利用性を確認してみましょう。
import React from "react";
import Header from "./Header";
export default class Layout extends React.Component {
render() {
return (
<div>
<Header />
+ <Header />
+ <Header />
</div>
);
}
}
するとHeader コンポーネントが3 つブラウザに表示されるようになります。
このようにReact のJSX はコンポーネント化することにより、コードの再利用性を高めながらアプリケーションを構築していくことができるようになるのです。
Footer コンポーネント
次はFooter コンポーネントを作成していきます。
import React from "react";
export default class Footer extends React.Component {
render() {
return (
<footer>footer</footer>
);
}
}
これを先ほどと同様にLayout.js に取り入れていきます。
import React from "react";
import Header from "./Header";
+import Footer from "./Footer";
export default class Layout extends React.Component {
render() {
return (
<div>
<Header />
- <Header />
- <Header />
+ <Footer />
</div>
);
}
}
Web ブラウザを確認すると、footer も表示されるようになりました。
補足: コンポーネントを複数個並べる場合の注意点
コンポーネントを同列に複数個並べる場合、次のように配列を作成して利用することもできます。
import React from "react";
import Header from "./Header";
import Footer from "./Footer";
export default class Layout extends React.Component {
render() {
let components = [<Header />, <Footer />];
return (
<div>
{components}
</div>
);
}
}
コンポーネントからコンポーネントを呼び出す
Header コンポーネントを先程作成しましたが、このコンポーネントから他のコンポーネントを呼び出すこともできます。
一般的にコンポーネントから別のコンポーネントを呼び出す場合、別のコンポーネントを格納するためのディレクトリを作成するのが一般的です。
今回はheader エリアにh1 要素を使ってタイトルを表示サせるためにTitle コンポーネントを作成しましょう。
まずはsrc/js/components/Header
ディレクトリを作成し、その中にTitle.js ファイルを作成してみましょう。
$ mkdir -p src/js/components/Header
import React from "react";
export default class Title extends React.Component {
render() {
return (
<h1>Welcome!</h1>
);
}
}
Title コンポーネントを作成したら、Header コンポーネントからTitle コンポーネントを参照するようにしてみましょう。
import React from "react";
+import Title from "./Header/Title";
export default class Header extends React.Component {
render() {
return (
- <header>header</header>
+ <header>
+ <Title />
+ </header>
);
}
}
Web ブラウザを開いて表示を確認してみましょう。Header にタイトルが表示されていることでしょう。
以上でコンポーネントについての説明は終わりです。
レイアウトのそれぞれのパーツをコンポーネント化することで再利用性が増し、開発効率とデザインの統一性の向上を期待することができます。
このように上手に工夫して再利用なコンポーネントを作成し、大規模なWeb 案件でも効率よくアプリケーションを開発していきましょう。
React のstate とライフサクル
React はstate という状態データを持ちます。
このstate はアプリケーションの状態を保持するもので、コンポーネントをどのようにレンダリングするかといった情報を格納する場所になります。
またこのstate はsetState を通じて変更されることで、state の変更をトリガーに再レンダリングの命令がキューイングされ自動的にコンポーネントの(再)レンダリングがされるようになります。
またsetState メソッドはstate の状態を変更する特に基本的な方法で、React でstate を変更して再レンダリングさせる時によく登場してくるメソッドになります。
ここではこのReact とstate の特徴について勉強し、反応性のあるアプリケーション作成について勉強していきましょう。
state について
state はReact コンポーネント内にあるのでReact.Component
クラスを親に持つクラスからthis.state
でアクセスすることができます。
それでは実際に組んでいきましょう。
下記のようにコンストラクタを定義してthis.state
初期値を設定し、setTimeout 使って初期画面表示から1000 ミリ秒後に画面の表示を更新するプログラムです。
import React from "react";
import Header from "./Header";
import Footer from "./Footer";
export default class Layout extends React.Component {
+ constructor() {
+ super();
+ this.state = {name: "Tsutomu"};
+ }
render() {
+ setTimeout(
+ () => { this.setState({name: "Hello"}); }
+ , 1000
+ );
return (
<div>
+ {this.state.name}
<Header />
<Footer />
</div>
);
}
}
このthis.state のデータはsetState を通して値が設定されたり変更されると、自動的に更新差分を検知し、render メソッド内のJSX によって必要なところだけdom が再レンダリングされるようになります。
ソースコードを書き換えたらnpm start
してhttp://localhost:8080
を確認してみましょう。
dom が動的に変換されました。
より視覚的にわかりやすくするために、Chrome の開発者モードをF12
キーで開きESC
キーを押してRendering
を選択し、Paint flashing
にチェックを入れて画面を確認してみてください。
すると、リアルタイムに状態が変わったdom を色付きで表示することができます。
この表示を見てわかるようにReact は再レンダリングする時に全体を更新するのではなく、必要な場所だけを更新するように動きます。
該当のソースコードの場所を見てみると…
......
return (
<div>
{this.state.name}
<Header />
<Footer />
</div>
);
......
と書かれており、一見するとthis.state.name
の文字列部分以外にもHeader やFooter コンポーネントも一緒に更新されてしまうように思われますが、実際に変更されるのはthis.state.name
の部分だけということが先程の表示で確認できました。
このようにReact はフロントエンドのjavascript の中でもコストの高いdom の描写においても必要部分のみ更新するので、細かい処理を記述しなくとも効率の良いレンダリング処理を実装することができるのです。
props について
HTML タグでは属性というタグ要素に対してパラメータを指定することで同じ要素でも、特定のものだけサイズを変えたり色を変えたりイベントを登録したり…といった細かい違いをもたせることができるようになっています。
React のJSX でも同様に各コンポーネントに対してパラメータを渡して使うことができ、そうすることでコンポーネント毎に個別の値の引数を渡すことができるようになります。
それをReact のJSX ではProps と呼びます。
それではさっそくProps を使ったプログラミングをしてみましょう。
import React from "react";
import Header from "./Header";
import Footer from "./Footer";
export default class Layout extends React.Component {
- constructor() {
- super();
- this.state = {name: "Tsutomu"};
- }
render() {
- setTimeout(
- () => { this.setState({name: "Nakamura"}); }
- , 1000);
+ const title = "Welcome Tsutomu!";
return (
<div>
- {this.state.name}
- <Header />
+ <Header title={title} />
<Footer />
</div>
);
}
}
上記のフィアルの<Header title={title} />
のtitle={title}
がprops になります。
次にHeader.js ファイルを開きましょう。
Header.js では、Layout.js から渡されたprops はthis.props
を通じてアクセスすることができます。
import React from "react";
import Title from "./Header/Title";
export default class Header extends React.Component {
render() {
+ console.log(this.props);
return (
<header>
<Title />
</header>
);
}
}
Web ブラウザの開発者ウィンドウを確認するとthis.props
が次のように表示されます。
文字列を渡したい場合は{"string"}
という形式で渡すようにします。
import React from "react";
import Header from "./Header";
import Footer from "./Footer";
export default class Layout extends React.Component {
render() {
const title = "Welcome Tsutomu!";
return (
<div>
- <Header title={title} />
+ <Header name={"some string"} title={title} />
<Footer />
</div>
);
}
}
また、次のようにHeader コンポーネントを2 つ作成して異なるprops を渡せば、それぞれ異なるprops を持ったHeader コンポーネントが呼ばれることになります。
import React from "react";
import Header from "./Header";
import Footer from "./Footer";
export default class Layout extends React.Component {
render() {
const title = "Welcome Tsutomu!";
return (
<div>
- <Header name={"some string"} title={title} />
+ <Header title={title} />
+ <Header title={"Thank you"} />
<Footer />
</div>
);
}
}
Props の外要を理解できたところで次はHeader コンポーネントからTitle コンポーネントへprops を渡してみましょう。
import React from "react";
import Title from "./Header/Title";
export default class Header extends React.Component {
render() {
- console.log(this.props);
return (
<header>
- <Title />
+ <Title title={this.props.title} />
<header>
);
}
}
Title コンポーネントでは次のように記載し、title props で渡された値を<h1>
タグで表示します。
import React from "react";
export default class Title extends React.Component {
render() {
return (
- <h1>Welcome!</h1>
+ <h1>{this.props.title}</h1>
);
}
}
これを保存してhttp://localhost:8080
でWeb ブラウザから画面を確認してみましょう。
2 つの異なるタイトルのコンポーネントが表示されます。
このようにしてprops を利用することで1 つのコンポーネントの再利用性を格段に向上させることができます。
state とprops の組み合わせ
state とprops を組み合わせて使用することもできます。
Layout.js を下記のように変更してみましょう。
import React from "react";
import Header from "./Header";
import Footer from "./Footer";
export default class Layout extends React.Component {
+ constructor() {
+ super();
+ this.state = {title: "Welcome"};
+ }
render() {
- const title = "Welcome Tsutomu!";
+ setTimeout(
+ () => { this.setState({title: "Welcome Tsutomu!"}); },
+ 2000
+ );
return (
<div>
- <Header title={title} />
+ <Header title={this.state.title} />
<Header title={"Thank you"} />
<Footer />
</div>
);
}
}
Props としてstate の値を指定しています。
このようにすることで、1 つ目のHeader コンポーネントのみ、表示から2 秒後にタイトルが変更されるようになります。
Event とデータ変更について
input 要素を追加してユーザがフォームに入力したデータによって、リアルタイムにイベントを発生させてみましょう。
Header.js を開いてinput を追加しましょう。
import React from "react";
import Title from "./Header/Title";
export default class Header extends React.Component {
render() {
return (
<header>
<Title title={this.props.title} />
+ <input />
</header>
);
}
}
あと前の編集でLayout.js に2 つHeader コンポーネントが記載されているので1 つ削除しましょう。
import React from "react";
import Header from "./Header";
import Footer from "./Footer";
export default class Layout extends React.Component {
constructor() {
super();
this.state = {title: "Welcome"};
}
render() {
setTimeout(
() => { console.log("called"); this.setState({title: "Welcome Tsutomu!"}); },
2000
);
return (
<div>
<Header title={this.state.title} />
- <Header title={"Thank you"} />
<Footer />
</div>
);
}
}
http://localhost:8080
を確認してみましょう。
すると次の画面のようにTitle にinput が追加された状態になります。
ただ、このままではフォームに文字を入力しても何もイベントが発生しません。
次はこのinput フォームに入力した値を取得して、タイトルをリアルタイムにレンダリングしていく処理を追加していきましょう。
input の内容を取得する処理の追加
Layout.js にchangeTitle メソッドを作成し、changeTitle メソッドをHeader コンポーネントへ渡すようにしてみましょう。
import React from "react";
import Header from "./Header";
import Footer from "./Footer";
export default class Layout extends React.Component {
constructor() {
super();
this.state = {title: "Welcome"};
}
+ changeTitle(title) {
+ this.setState({title});
+ }
render() {
- setTimeout(
- () => { console.log("called"); this.setState({title: "Welcome Tsutomu!"}); },
- 2000
- );
return (
<div>
- <Header title={this.state.title} />
+ <Header changeTitle={this.changeTitle.bind(this)} title={this.state.title} />
<Footer />
</div>
);
}
}
上記のchangeTitle 関数のthis.setState({title});
という書き方はES6 の書き方で、this.setState({title: title});
と同じ意味になります。
また、<Header changeTitle={this.changeTitle.bind(this)} ...>
としてメソッドをprops でHeader コンポーネントに渡すことで、Header.js 内でLayout.js のchangeTitle(title)
メソッドを呼び出すことができるようになります。
次にHeader.js を編集します。
下記のようにすることで、Header コンポーネントとTitle コンポーネントはLayout コンポーネントから渡された値をそのまま表示すればよく、Layout コンポーネントからどんな値がどんなタイミングで渡されるかを管理する必要がなくなります。
import React from "react";
import Title from "./Header/Title";
export default class Header extends React.Component {
+ handleChange(e) {
+ const title = e.target.value;
+ this.props.changeTitle(title);
+ }
render() {
console.log(this.props);
return (
<header>
<Title title={this.props.title} />
- <input />
+ <input value={this.props.title} onChange={this.handleChange.bind(this)} />
</header>
);
}
}
この状態でhttp://localhost:8080
を開いてinput にテキストを入力してみましょう。
するとinput に入力した値がリアルタイムにTitle に反映され、画面に描写されるようになります。
ここではReact を使った基本的な状態管理とレンダリングについて勉強していきました。
React のstate が変更されると、状態が即座に再レンダリングされるイメージがつきましたでしょうか。
この時点でReact を使った簡単なアプリケーション作成については問題なく実施できるようになっています。
次からはReact Router を使ってコンテンツのリスト表示や異なるパスのWeb ページをSPA で実現する手法について勉強していきます。
補足: Layout コンポーネントのbind(this) という記述について
Layout コンポーネントからHeader コンポーネントへメソッドを渡す時に<Header changeTitle={this.changeTitle.bind(this)} ...>
というようにbind(this) メソッドを呼び出して渡していますが、これをやらないで<Header changeTitle={this.changeTitle} ...>
とした場合、関数自体はHeader コンポーネントへ渡りますが、その関数をHeader コンポーネントから呼び出した時、その関数はLayout スコープで呼ばれた関数とは別の関数になってしまいます。
そのため、Header コンポーネントにchangeTitle 関数を渡して呼び出すと…
changeTitle(title) {
this.setState({title});
}
上記のthis.setState({title});
のthis
が、Layout インスタンスでは無くなってしまいます。
その結果setState
関数もLayout クラス内の関数では無くなってしまい、想定しない動作をする可能性があります。
そのため、確実にLayout インスタンスのsetState 関数を呼び出すために<Header changeTitle={this.changeTitle.bind(this)} ...>
とbind 関数の引数のthis (Layout インスタンス)に対して紐付けをしてあげることで、この関数がHeader コンポーネント内、またはその他のあらゆる場所で呼ばれたとしてもLayout インスタンスのchangeTitle
関数が呼ばれるようになるのです。
また当然ですが<Header changeTitle={this.changeTitle.bind(someInstance)} ...>
とすれば、someInstance スコープで呼ばれるchangeTitle 関数を渡すこともできます。
補足: public class fields syntax によるbind の記載の省略
public class fields syntax を使用することでbind の記載を省略することができます。
...
changeTitle = (title) => { /* <- 関数の宣言をこのように変える */
this.setState({title});
}
render() {
return (
<div>
<Header changeTitle={this.changeTitle} title={this.state.title} /> /* <- bind の記載が省略できる */
<Footer />
</div>
);
}
- 2019-04-30 @6in さんのコメントより追記:
public class fields syntax を使うには@babel/plugin-proposal-class-properties
が必要になりますのでインストールしてplugin として.babelrc
もしくはwebpack.config.js
に指定するようにしてください。
$ npm install --save-dev @babel/plugin-proposal-class-properties
{
"plugins": [
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}
......
use: [{
loader: 'babel-loader',
options: {
- presets: ['@babel/preset-react', '@babel/preset-env']
+ presets: ['@babel/preset-react', '@babel/preset-env'],
+ plugins: [
+ ['@babel/plugin-proposal-class-properties', { 'loose': true }]
+ ]
}
......
完了後、npm start
を再実行してください。
補足: arrow function によるbind の記載の省略
アロー関数を使うことでもbind の記載を省略することができます。
...
changeTitle(title) {
this.setState({title});
}
render() {
return (
<div>
<Header changeTitle={(e) => this.handleClick(e)} title={this.state.title} />
<Footer />
</div>
);
}
この記載法の欠点は大抵の場合は問題にはなりませんが、注意点があります。
この方法だと、render メソッドが呼ばれるたびにchangeTitle 内の関数が毎回生成されることになります。
また、サブコンポーネントはprop 経由且つこの記法で関数が渡された場合、独自のshouldComponentUpdate() を記述しない限り、常にサブコンポーネントは再レンダリングされることになります。
これらの問題のため一般的にはbind もしくはpublic class fields syntax を使う方法が一般的に推奨されています。
参考
-
React A JavaScript library for building user interfaces
-
REACT JS TUTORIAL #1 - Reactjs Javascript Introduction & Workspace Setup
-
REACT JS TUTORIAL #2 - Reactjs Components & Rendering
-
REACT JS TUTORIAL #3 - Composing Multiple React.js Components
-
Best way to build/compile/deploy ReactJS to production
-
Handling Events
-
Function.prototype.bind()
-
Public Class Fields saves sooo many keystrokes in React code