この記事について
プロダクト開発でReactを使い始めてそろそろ半年ほどになります
やっといい感じに扱えるようになってきたので、ノウハウを記事にしていこうかなと思っています
Reactって使い始めると便利なのですが、一つ大きな問題があります
そう、それは導入障壁が大きすぎること
Node入れて、Babel導入してJSX/ES2015でコーディングして、タスクランナー導入してビルド自動化して、Redux導入して...
巷のReact記事を読むとこんな感じの内容ばかりで思わず萎縮してしまうことでしょう
というわけで、この記事では「React使ってみたい、でもNodeとかES2015とかよくわかんない」って人向けのReact入門講座を進めていこうと思います
いきなりReact+Reduxのサンプルが出てきたりしてちょっと混乱してる方や、
JSX使ってBabelでコンパイルしてるけどReactの基礎的なところからやり直したいって方もぜひどうぞ
この記事を読めば、Reactと言えど結局はただのJSで実装されていることがわかると思います
なるべく余分な要素をそぎ落として、React自体について考える手助けになることを願っています
Reactを使う準備をしよう
雛形のhtmlファイルを用意する
基本的にこの記事ではReactの使い方自体にフォーカスしていきたいので、環境構成は最小限に抑える方針で進めていきます
まずは、https://facebook.github.io/react/ からReact.jsをダウンロードしましょう
今回はReact v0.14.7を利用します
(余談ですが、Reactはv0.14.7の次からv15.x.xという形にバージョンの取り方が変わりました
確認はしていませんが、この記事に載っているサンプルならv15系でも動くと思います)
もしくは、cdnjs などを利用しましょう
https://cdnjs.com/libraries/react/0.14.7
本記事ではこちらの方法で進めていきます
では、雛形となるhtmlファイルを作成します
<html>
<head>
<meta charset="UTF-8" />
<title>Minimal React</title>
</head>
<body>
<div id="app"></app>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.js"></script>
<script>
// ここにJS書いていくよ
</script>
</body>
</html>
注意点として、react.js
に加えてreact-dom.js
も読み込む必要があります
とりあえず、何か表示してみよう
次に恒例のHello, World
でもやってみましょう
最後のscriptタグの中に次のコードを加えてください
rootElement = React.createElement('div', {}, "Hello, World")
ReactDOM.render(rootElement, document.getElementById('app'))
Hello, World
と表示されたでしょうか
表示されれば準備OKです
Reactで自在にDOMを生成する
さて、まずは先ほどのHello, World
のコードを見てみましょう
React.createElement
は要素を生成するAPIです
第一引数にhtml要素名、第二引数にhtml属性、第三引数以降に要素の中身を指定します
React本家のリファレンスを見たら、戻り値はReactElement
と呼ばれているようなので、この記事でも以降ReactElementで統一したいと思います
React.createElement
で生成されたReactElementは、ReactDOM.render
でDOMにアタッチされます
第一引数にDOMにアタッチするReactElement、第二引数に実際のDOM内に準備したコンテナを指定します
つまり、今回の場合では
<div id="app">
<div>Hello, World</div>
</div>
みたいな感じんなるわけですね
ここの読み替えはすっとできるように感覚に落とし込んでいきましょう
ここまでが、Reactの第一歩目になります
次に、scriptタグ内を書き換えてもう少し複雑なDOMツリーを生成してみましょう
rootElement = React.createElement('div', {},
React.createElement('h1', {}, 'Brand'),
React.createElement('ul', {},
React.createElement('li', {},
React.createElement('a', { href: '/about' }, 'About')
),
React.createElement('li', {},
React.createElement('a', { href: '/news' }, 'News')
),
React.createElement('li', {},
React.createElement('a', { href: '/contact-us' }, 'Contact us')
)
)
)
ReactDOM.render(rootElement, document.getElementById('app'))
ヘッダーのグローバルナビゲーションをイメージして作ってみました
ポイントとしては、React.createElement
の第三引数以降にはいくつでもReactElementまたは文字列を渡すことができる点でしょうか
つまり、createElement
は上記のように入れ子みたいにして書けるわけですね
さて、上の結果生成されるhtmlはどのようになるでしょうか?
<div id="app">
<div>
<h1>Brand</h1>
<ul>
<li><a href="/about">About</a></li>
<li><a href="/news">News</a></li>
<li><a href="/contact-us">Contact us</a></li>
</ul>
</div>
</div>
こんな感じですね
コンポーネント - 自作の要素を用意する
だいたいReactの感覚はつかめてきたでしょうか
次は、複数のReactElementを組み合わせた自作要素を定義してみましょう
Reactではコンポーネントと呼ばれるやつですね
コンポーネントを定義することで、要素の再利用が可能になります
早速、先ほどのナビゲーションをコンポーネント化してみましょう
var GlobalNavigation = React.createClass({
render: function() {
return React.createElement('div', {},
React.createElement('h1', {}, 'Brand'),
React.createElement('ul', {},
React.createElement('li', {},
React.createElement('a', { href: '/about' }, 'About')
),
React.createElement('li', {},
React.createElement('a', { href: '/news' }, 'News')
),
React.createElement('li', {},
React.createElement('a', { href: '/contact-us' }, 'Contact us')
)
)
)
},
})
var rootElement = React.createElement(GlobalNavigation, {})
ReactDOM.render(rootElement, document.getElementById("app"))
createClass
はコンポーネントを定義するためのAPIです
(ES2015導入環境ではclass
構文を使いReact.Component
を継承します)
第一引数にオブジェクトを指定します
戻り値はReactClassと呼ばれているようです
第一引数について、render
というメソッドを定義してやります
読んで字のごとくコンポーネントの描画用のメソッドです
render
内でreturn
したReactElementがこのコンポーネントの中身となります
続いて、React.createElement
にMyComponent
を渡します
先ほどまでは'div'
などの要素名を指定していましたが、コンポーネントを定義すると要素名と同様にReactClassを第一引数に指定することができます
コンポーネント化を行うことで、コンポーネントの再利用が可能になります
例えば、以下のような例を考えてみましょう
rootElement = createElement('div', {},
React.createElement(GlobalNavigation, {}),
React.createElement(SideBar, {}),
React.createElement('div', {} 'This is main contents'),
React.createElement(Footer, {})
)
こんな形でナビゲーションやフッターなどを共通化できるわけですね
便利!
状態変化のあるアプリケーションを作る
さて、いよいよアプリケーションっぽいものを作る準備が整いました
ここではReactにおいて状態を管理するための方法を紹介します
簡単なカウンターを作ってみましょう
仕様としては+1ボタンを押すとカウントアップ、-1を押すとカウントダウンといったところでしょうか
var CounterApp = React.createClass({
getInitialState: function() {
return {
count: 0,
}
// 2016/05/09 訂正
// コメントアウトした部分は間違ってました
// 上記のように記述するのが正解です
// this.state = {
// count: 0,
// }
},
render: function() {
return React.createElement('div', {},
React.createElement('div', {}, "count: " + this.state.count),
React.createElement('div', {
onClick: function() {
this.setState({
count: this.state.count + 1
})
}.bind(this),
}, "+1"),
React.createElement('div', {
onClick: function() {
this.setState({
count: this.state.count - 1
})
}.bind(this),
}, "-1")
)
}
})
var rootElement = React.createElement(CounterApp, {})
ReactDOM.render(rootElement, document.getElementById("app"))
ちょっと今回は説明することが多いです
まずはCounterApp
というReactClassを作成しています
CounterApp
にgetInitialState
というメソッドを定義しています
Reactでは状態変数のことをstateと呼んでいます
その初期値を返すのがgetInitialState
というわけですね
今回はカウンターの値を保持するためのcount
プロパティの初期値を0にしています
ここで初期化したstate
は、ReactClass内ではthis.state
として参照することができます
render
メソッドを見てみましょう
"count: " + this.state.count
として先ほど初期化したstateを参照しています
また、その下にボタン用のdiv要素を二つ用意しています
それぞれ第二引数にonClick
ハンドラを指定しています
Reactではこのようにしてイベントの追加ができるわけですね
イベントハンドラの中ではthis.setState
というメソッドを呼んでいます
これはstateを更新するためのメソッドなのですが、this.setState
が呼ばれるとReactClassは仮想DOMと呼ばれる仕組みを利用して自身の状態を自動で更新します
詳細は後ほど述べようと思いますが、とりあえず今は「状態更新するにはsetState
」と覚えてください
※ this.state
の値を直接変更してはいけません
このサンプルを実際に実行していただければ、クリックするだけで自動でDOM状態が更新されることが確認できると思います
これはクリックによってsetState
が実行されstateが更新され、その更新に基づいて再びReactClassがrenderメソッドを実行しDOM更新を行ってくれるためです
このようにDOM操作をReactが隠匿してくれるおかげで、我々は状態の管理に集中することができるのです
この記事で掲載するサンプルはこれで最後にしようと思います
state
とは別にprops
なども説明したいのですが、今回はここまでとします
最後に少し仮想DOMについてお話をして終わりにしましょう
仮想DOMとは
先ほども少し触れた仮想DOMですが、1, 2年ほど前(ちょっと正確にはわからないです)に登場し非常に注目を集めてきました
それは、今までのフロントエンドの状態管理へのアプローチを一変させるポテンシャルを持っていたからです
かつてのフロントエンドは、DOMイベントなどが発生するたびにDOMの一部を書き換えて状態を変更するという実装が主流でした
代表例はjQueryですね
しかし、この実装方法には「DOM自身が状態を持ってしまうので、状態が様々な場所に散り散りになり管理が大変」という問題がありました
SPA(Single Page Application)などの需要が高まるに連れて、この問題はより深刻になりフロントエンドエンジニアを苦しめていきます
特定の状態の時にしか発生しないバグなどの温床ですからね
フロントエンドな方なら一度は頭を悩まされたのではないでしょうか
このような問題を解消しようと、状態を一箇所に集めておき状態の更新が起こるたびにDOM全体を描画し直すという実装をしようという流れが生まれてきます
しかし、想像に難くない通り、ただでさえ重いDOM操作をページ全体で行うのはパフォーマンス的に考えて現実的ではありません
そこで、仮想DOMの出番です
仮想DOMとはJS上でDOMのツリー構造を再現したものです
これが全体更新を可能にするわけです
状態の更新が発生すると、Reactはまず実際のDOMに変更を加えるのではなく、仮想DOM上でツリー構造を再現します
そして、更新前の仮想DOMツリーと比較しその差分だけを抽出します
そのあと、差分のみの変更を実際のDOMに対して行います
つまり、Reactでは全体更新を行っても、いい感じに最小のDOM操作だけを行ってくれるわけです
この仮想DOMの登場によってフロントエンドもページ全体を毎度書き換えるというアプローチが可能になったわけですね
さらに全体更新というアプローチが可能になったことで、我々は細かいDOM操作については考える必要がなく、状態からDOMを生成することだけに集中すればよいことになります
Reactにおいてはコンポーネントがこの役割を担います
コンポーネントはstateからDOMツリーを生成するある種の関数のようなものとして解釈することができます
イメージとしてはstate => (f) => DOM
みたいな感じですかね
stateをコンポーネントに通すとDOMが出来上がる的な感覚だと自分は思っています
あるいは、コンポーネントをPHP
やRuby
のerb
テンプレートなどのように考えてもいいと思います
状態を与えてあげたらHTMLドキュメントを生成してくれる点でコンポーネントと本質的には同じです
ただし、コンポーネントは単純にドキュメントを生成するのに比べてイベントのバインドなども行ってくれるわけですが
こういったわけで、仮想DOMによって今まで不可能だった全更新が可能になりフロントエンドに新たな可能性が生まれたのです
こうした中で生まれたフロントエンドアーキテクチャの一つがFlux
だったりするわけですね
(Flux
実装もたくさんあるので自分もあんまり追えてないです...
というか、全般的に流れが速すぎてフロント界隈に追いつけてない感じがします)
最後に
ここで紹介した内容ぐらいを押さえておけば本家のReactチュートリアルなんかを見ても多分ついていけるのではないでしょうか
ちなみに、今回紹介したぐらいのコードでJSからどんなDOM構造が出来上がるか変換ができれば、JSXなんて1ミリも怖くないですし、むしろ脳内変換する手間が省けるので便利です
まだまだこれからAjaxによる非同期処理が入ってきたり、より複雑なアプリケーションを作る上でどんな設計をしていくのかは考えなければならないことがたくさんあるのですが、そのあたりは巷のいろんなサンプルを見たり実際に試行錯誤していくしかないかなと思います
というわけで、この記事がReactに入門する上での手助けになることを願ってこの記事を締めくくりたいと思います