免責事項: 私はJSX、Flux、ES6、そしてwebpackを非常に気に入っています。これらのツールについては他のシリーズで話します。
React.jsが騒ぎを起こしているのはご存知の通りです。確かに、XMLHttpRequest
以来の良いツールです。しかし、調査に数時間を費やした挙句、あまりに多くの用語に圧倒されただけで終わっていないでしょうか。JSX、flux、ES6、webpack、react-routerが使える今、他に必要なのはReactの使い方を説明してくれる人だけです。
喜んでください、それがまさに当シリーズでやろうとしていることです。信じられませんか?大丈夫、2分後、初めてのReactアプリを作った後には納得いただけるでしょう。何もダウンロードせずに、です。次の練習をやってみてください。
練習 1 シングルファイルのReact.jsアプリを書こう
JavaScript、CSS、HTML を使ったことはありますか。いいですね。それならこの練習ができます。
この練習の成果物は、あなたが作る最初のReactアプリを含むHTMLファイル1点です。3つのステップがあります。
ステップ 1
このHTMLを新規ファイルにコピーし、適当な場所に保存しましょう。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>I'm in a React app!</title> </head> <body> <div id="react-app"></div> <script src="https://cdn.jsdelivr.net/react/0.14.0-rc1/react.js"></script> <script src="https://cdn.jsdelivr.net/react/0.14.0-rc1/react-dom.js"></script> </body> </html>
このHTMLファイルには2つの重要な役割があります。レンダリングしたコンテンツ(idreact-app
と共に)が動くdiv
を作成し、Reactが動く2つのライブラリファイルをロードします。
コピーできたら、次に進みましょう。
ステップ 2
HTMLファイルの末尾に、次のスクリプトを新規<script>
タグに入力します。
var rootElement = React.createElement('div', {}, React.createElement('h1', {}, "Contacts"), React.createElement('ul', {}, React.createElement('li', {}, React.createElement('h2', {}, "James Nelson"), React.createElement('a', {href: 'mailto:james@jamesknelson.com'}, 'james@jamesknelson.com') ), React.createElement('li', {}, React.createElement('h2', {}, "Joe Citizen"), React.createElement('a', {href: 'mailto:joe@example.com'}, 'joe@example.com') ) ) ) ReactDOM.render(rootElement, document.getElementById('react-app'))
ちょっとした仕事ですね。しかし、繰り返し入力したことで、createElement
メソッドの概念がつかめるとよいのですが。まだ見えてこない、という人にはステップ3が役立つでしょう。
ステップ 3
WebブラウザでHTMLファイルを開き、アウトプットが次のようになっていることを確認してください。
どうでしょうか。良かった、分かりましたね。あなたは初めてのReact.jsアプリをnpm
さえインストールせずに作ってしまいました。お祝いを兼ねて、その機能を見ていきましょう。
Reactの導入
基本的に、ReactはHTMLをJavaScriptでレンダリングするツールです。ReactDOM.render
は、ReactElement
オブジェクトを使って、何をレンダリングするかを説明し、指定されたDOM nodeに結果を追加します。しかし、ReactElement
はどうやって作るのでしょうか。ここでReact.createElement
の出番です。
React.createElement
は3つのパラメータを引数にとり、ReactElement
を返します。Reactのドキュメンテーションにはこのように記述されています。
createElement(string/ReactClass type, [object props], [children ...]) -> ReactElement
type
引数が、使用するHTML要素のタイプを指定します。また、カスタム要素も指定します(後で説明します)。props
引数はどの属性がHTML要素に定義されているかを指定する方法です。練習に出てきたmailto
リンクから推測できたかもしれません。最後に、children
引数は、返された要素のコンテンツに使われる文字列、ReactElement
オブジェクト(あるいはその配列)です。children
を除外するか指定するか選ぶことで、ReactElement
1つか、ツリー全体を返します。
createElement
は素のJavaScriptなので、ループ、if
文、その他JavaScriptで行えるものなら何でも挿入できます。さらに、JSONに格納されたデータに簡単に代入することも可能です。例えば、連絡先のリストを持っているものの、e-mailアドレスのないコンタクトはレンダリングしたくない場合、次のような記述が考えられます。
var contacts = [ {key: 1, name: "James Nelson", email: "james@jamesknelson.com"}, {key: 2, name: "Bob"} ] var listElements = contacts .filter(function(contact) { return contact.email; }) .map(function(contact) { return React.createElement('li', {key: contact.key}, React.createElement('h2', {}, contact.name), React.createElement('a', {href: 'mailto:'+contact.email}, contact.email) ) }) var rootElement = React.createElement('div', {}, React.createElement('h1', {}, "Contacts"), // If your `children` is an array, you'll need to give each one a unique `key` // prop. I'll explain why a little later. React.createElement('ul', {}, listElements) ) ReactDOM.render(rootElement, document.getElementById('react-app'))
すっきりしています。つまりReactは非常に多弁なJavaScriptベースのテンプレート化システムであると言えます。しかし… それでは熱心に評価される理由になりません。何が特長なのでしょうか。
コンポーネント
React.createElement
のタイプシグネチャ内の小さなReactClass
に気づいたでしょうか。使用しないので省きましたが、何を意味するか分かりますか。
ドラムロールの音をください、答えです。「React.createElement
は標準HTMLに限定されない」。オリジナルのコンポーネントを作ることもできるのです。
どうやって? React.createClass
を使って。
var ContactItem = React.createClass({ propTypes: { name: React.PropTypes.string.isRequired, }, render: function() { return ( React.createElement('li', {className: 'Contact'}, React.createElement('h2', {className: 'Contact-name'}, this.props.name) ) ) }, });
この小さなスニペットはContactItem
と呼ばれる新規コンポーネントを定義します。HTML要素と同様に、ContactItem
は属性のリスト(props
と呼びます)を受け入れます。HTML要素と異なるのは、どんなprops
でも指定できることです。
上述のコンポーネントは次のように使えます。
var element = React.createElement(ContactItem, {name: "James K Nelson"})
そして予想どおり、element
にはコンポーネントのrender
関数が返す値が含まれます。
children
引数をcreateElement
に渡す場合、その値はthis.props.children
の下で取得できます。
次の練習で理解度をテストしてみましょう。
練習 2 コンタクトリストのリファクタリング
学習したことを使って、練習1で書いた答え、特に次の点をリファクタリングしましょう。
- 3つのプロパティ、
name
、email
とdescription
を受け入れるContactItem
クラスを作る。 - e-mailアドレスを持たないコンタクトを選別する。
- データを
createElement
に直接渡す代わりに、配列に格納する。
次のデータを使ってください。
var contacts = [ {key: 1, name: "James K Nelson", email: "james@jamesknelson.com", description: "Front-end Unicorn"}, {key: 2, name: "Jim", email: "jim@example.com"}, {key: 3, name: "Joe"}, ]
終わったら、次のfiddleと照らし合わせてみてください。
propTypes
練習 2の私の答えでは、propTypes
オブジェクトに2種類の値、isRequired
が付いている値と付いていない値を使っていることに気づいたでしょうか。このサフィックスは、指定されたプロパティが必ずReact.createElement
に渡されなければならないことを示しており、Reactの全てのprop typesに適用できます。
しかし、ちょっと待ってください。propTypes
オブジェクトは実際のところ何をしているのでしょうか。
ほとんどの場合、 propTypes
は何もしていないというのが答えです。事実、完全になくしてもアプリは正確に機能します。ではなぜそこにあるのでしょう。
propTypes
はデバッグを行うメカニズムなのです。これによってReactはcreateElement
に渡されたprops
引数が有効かどうかをリアルタイムでチェックします。正しくない場合はReactはコンソールに警告を出します。
この手引きで propTypes
を紹介するのは、それが必須事項ではなくとも、長い目で見ると多くの無駄を防ぐことになるからです。そして私のニュースレターに登録するとダウンロードできる、Cheat Sheet(PDF)を印刷しておけば、より労力を軽減できます。話を元に戻します。ここまででデータを表示させる方法が分かりました。次に更新の仕方を学びましょう。
Get the PDF cheatsheet!
新規データを表示させる
現状、私たちのアプリは静的データ一式を取り扱い、1度だけDOMへレンダリングします。しかし、データに変化がある場合には何が起こるのでしょうか。
Reactと呼ばれるツールにどのような期待をしようと、何も変わりません。他の多くのJavaScriptフレームワークの自動更新ビューとは異なり、Reactは手動でレンダリングの命令を出さなければなりません。その方法を説明します。
// Render our app to the DOM element with id `react-app` var rootElement = React.createElement(ApplicationComponent, initialData) ReactDOM.render(rootElement, document.getElementById('react-app')) // Actually, there is no re-render. Just render. var nextRootElement = React.createElement(ApplicationComponent, updatedData) ReactDOM.render(nextRootElement, document.getElementById('react-app'))
最初のrender
関数の呼び出しによって、全く新しいHTML要素のツリーが#react-app
以下に作成されますが、2回目の呼び出しは変更が生じたところのみの変更しか行いません。これがパフォーマンスにおいてReactが好評価を得ている理由です。実際に次のことをしてみます。
var rootElement = React.createElement(ApplicationComponent, data) ReactDOM.render(rootElement, document.getElementById('react-app')) ReactDOM.render(rootElement, document.getElementById('react-app'))
2回目のrender
関数の呼び出しは全く何もしません。特別なkey
プロパティをそれぞれの要素の配列に指定してレンダリングしない限りは何もしません。なぜこうなのか理解するにはrender
がどのように動くか少し知る必要があります。
ReactDOM.render
の仕組み
ReactDOM.render
の最初の呼び出しは簡単です。ReactElement
内で渡され、対応のHTMLツリーを#react-app
の下に作成します。しかし上でも述べたように2回目以降の呼び出しでは、それ以前にレンダリングされたHTML要素は、対応するReactElement
オブジェクトに変更がない限り、更新されません。これはなぜなのでしょうか。
何を変更するのかを決定する時、Reactは新しいReactElement
ツリーと前のツリーを比較します。その際に、いくつかのルールに従います。
- 異なる型を持つ
ReactElement
は削除され、再度レンダリングされます。 - 異なるプロパティあるいは子を持つ
ReactElement
はその場で再度レンダリングされます。 - 配列順番が変更された同一の
ReactElement
は、新しい配列どおりに変更されます。
ただし、ここで注意すべき点が1つあります。Reactが同一のtype
とprop
を持つReactElement
の配列を見つけると、見た目は同一のようでも、本当に同一であるかは知ることはできません。例えば要素がuser focusプロパティを持っているか否かの場合のように、要素が内部状態を持つことができるからです。しかし、それぞれの要素の区別がつかなくなってしまうため、Reactがこれら要素を再度レンダリングすると問題が発生します。その結果、配列の順番が変更になったのか分からなくなってしまうのです。
前述の例のkey
プロパティはここで使用することになります。key
プロパティはReactが要素を区別できるようにし、DOMとReactElement
のツリーを整列しておきます。
理論はこのくらいにして、実用的なことに移りましょう。
フォーム
アプリにフォームを追加できるだけの知識が備わりました。ユーザインタラクションがなくてもいいのであれば、の話です。しかし、なぜこのような制約があるのでしょう。
Reactでは、input要素は特別のことではありません。value
プロパティを取り、そのvalue
が何であるかを表示します。コンポーネントが外部から渡されたプロパティを直接変更できないため、ユーザ入力によって値の表示を変更してしまうことはありません。
少しこれを考えてみましょう。value属性をフォームに入力しても、何も変わりません。ただし、値を変えられないわけではありません。input
イベントをリッスンして値を必要に応じて更新すれば好きな値に変更することができます。これについては後ほど触れます。
value
以外にもinput要素に持たせたいプロパティを持たせることが可能です。しかし、ちょっとした例外がいくつかあります。HTML <input>
に渡したい属性もprops
で提供されています。次の2つの例外は知っておくべきでしょう。
- Reactの
textarea
要素はchildren
としてではなく、props
としてコンテンツを持ちます。 - HTMLの
for
属性はhtmlFor
にバインドされています。(JavaScriptでは、for
は予約語です)。
私の作成したReact Cheat Sheetに上の例外は記載されています。私のニュースレターに登録した人にお配りしています。
練習 3 読み取り専用フォームの作成
では、次のpropTypes
を持つ、新しいContactForm
のコンポーネントクラスを作成します。
propTypes: { contact: React.PropTypes.object.isRequired },
<form>
要素をレンダリングすると、name
やemail
が入力できる<input>
要素が表示され、description
が入力できる<textarea>
が表示され、送信のための<button type="submit">
が表示されるはずです。複雑化を避けるためにもラベルの代わりにプレースホルダを使うといいでしょう。そして、Reactのtextarea
コンポーネントがvalue
をchildren
としてではなくprops
として受け取ることを覚えておきましょう。
ContactForm
クラスを作成した後、contact
プロパティとして渡される空白のコンタクトをコンタクトリストの下に追加してください。
できましたね。すばらしい。私の実装と比べてみてください。違いを見つけてほしいとは思いますが、私の実装に全ての行が一致する必要はないので、一致させることに固執しないでください。重要なのは動くものを書くことです。
アプリを美しくする
まだ記事を読み終わっていないのに、すでに売る気満々です。しかし、アプリの見た目が悪かったら誰も見向きもしません。そこで、ちょっとしたスタイルを追加しましょう。
どうすればいいのでしょう。このアプリは小さいので、HTMLセレクタを使用してスタイルを付けることもできます。
ul { background: #ff0000; }
しかし、別のリストを作ってしまうとアプリが動かなくなってしまいまので、後の苦労を避けるためにもクラスを指定しましょう。競合を避けるため、親コンポーネントのクラス名を名前空間に定義します。
ここでは、ContactItem
コンポーネントは次のようになります。
render: function() { return ( React.createElement('li', {className: 'ContactItem'}, React.createElement('h2', {className: 'ContactItem-name'}, this.props.name), React.createElement('a', {className: 'ContactItem-email', href: 'mailto:'+this.props.email}, this.props.email), React.createElement('div', {className: 'ContactItem-description'}, this.props.description) ) ) },
className
プロパティを使用してCSSクラスを指定します(JavaScriptではclass
は予約語です)。
コンポーネントのrender
関数の一部ではないcreateElement
呼び出しでクラス名を取得するにはどうすればいいのでしょうか。
ほとんどのReact系のプロジェクトでは、全てがクラスによって組織化されています。1つのReact.createElement
を呼び出し、直接ReactDOM.render
に渡されるトップレベルの要素をレンダリングします。このアプリでは、トップレベルのContactView
を次のシグネチャを使用しましょう。
propTypes: { contacts: React.PropTypes.array.isRequired, newContact: React.PropTypes.object.isRequired, },
練習4 おまけ
新規アプリにスタイルを付けたい場合、ContactView
を上で定義したとおりに作成して、className
プロパティを必要に応じて追加して、さらにHTMLファイルにスタイルシートを追加します。
どのようにすればいいかヒントをあげましょう。fiddleを用意してみました。
誰も読み取り専用のアプリを好まない
見た目の良いアプリを作成するために知識は揃いました。しかし、インタラクティブにする方法はどのようにして学べばよいのでしょうか。
この記事は生のReactシリーズの第一弾です。毎週新しい記事を掲載予定です。Reactで本物のアプリを実際に読者が作成するのに十分な情報を出せるまで書き続けます。本物のアプリの定義は次のとおりです。
- フォームイベントを処理できる。
- ナビゲーションやルーティングを処理できる。
- APIと通信できる
- ユーザ認証の管理ができる。
最新情報: Part 2: Ridiculously Simple Formsを掲載しました。
emailアドレスをくれた方には、新しい記事を発表のタイミングでお送りします。さらに特典としてReact、ES6、JavaScriptの約束事の3つの印刷用PDF Cheat Sheetを差し上げます。
I will send you useful articles, cheatsheets and code.
注釈:役に立つ記事やCheat Sheet、コードをお送りします。決して大量の無駄な情報を送るようなことはしません。絶対に迷惑メールは送りません。
感想をぜひ教えてください。@james_k_nelsonにツイートするか、 james@jamesknelson.comにメールしてください。読んでくださってありがとうございます。
さらに読む:
- Learn Raw React Part 2: Ridiculously Simple Forms
- Learn Raw React Part 3: Routing
*Interacting with the DOM in React
関連リンク: