みなさんこんにちは!DeNA Games Osaka 技術編成部のさいです。DeNA Games Osaka は DeNA の大阪拠点です。今後ともよろしくおねがいしますね!
前回、JavaScript の MVC フレームワーク Mithril についてご紹介させていただきました。
MVC の概念は、サーバーサイドエンジニアにとって親しみやすい概念である一方、これまでフロントエンドを触ってきた人たちにとって、取っ付きにくい概念ではありませんでしたでしょうか。
今回はそんなフロントエンドエンジニアの人たちにオススメの、コンポーネント指向なフレームワーク Riot.js についてご紹介させていただきますね。
Riot.js とは
React like を謳うコンポーネント指向な JavaScript フレームワークです。MVCの概念が、Model, View, Controller と処理の種類によってモジュールを切り分けていたのに対し、コンポーネント指向においては DOM をベースにモジュールを切り分けます。
クライアントサイドのMVCが、サーバーサイドのこれまでのMVCの概念と非常に親しく、サーバーサイドエンジニアにとって習得が容易であった一方、コンポーネント指向は、DOMあるいはHTMLタグの概念と非常に親しく、マークアップエンジニアやデザイナー向けの概念であると言えます。
軽い
v2.5.0時点で 9.25KB ほどです。React(v15.2.0)が 45.49KB なので、約5倍ほどのファイルサイズ差があります。(minify済のものを比較)
※画像は公式サイトよりお借りしました
これはモバイル端末などの貧弱な通信環境にて有利です。
React っぽい
React と Riot.js, 両者はコンポーネント指向という点で共通しています。React のコンポーネント指向の知識があれば、Riot.js も容易に学習できます。
一方、下記のReact と Riot.js のソースコードを比較してわかるように、Reactが「JavaScript の中に HTMLを含める」アプローチなのに対して、Riot.js は「HTML の中に JavaScriptを含める」アプローチという違いがあります。
React
import React from 'react'; import ReactDOM from 'react-dom'; class Todo extends React.Component { constructor(props) { super(props); this.state = {items: [], text: ''}; } render() { const {items, text} = this.state; return ( <div> <h3>TODO</h3> <ul> <li>{items.map((item, i)=> <li key={i}>{item}</li>)}</li> </ul> <form onSubmit={this._onSubmit}> <input onChange={this._onChange} value={text}/> <button>Add #{items.length + 1}</button> </form> </div> ); } _onChange(e) { this.setState({text: e.target.value}); } _onSubmit(e) { e.preventDefault(); const {items, text} = this.state; this.setState({ items: items.concat(text), text: '' }); } } ReactDOM.render(<Todo/>, mountNode);
Riot.js
<todo> <h3>TODO</h3> <ul> <li each={ item, i in items }>{ item }</li> </ul> <form onsubmit={ handleSubmit }> <input> <button>Add #{ items.length + 1 }</button> </form> this.items = [] handleSubmit(e) { var input = e.target[0] this.items.push(input.value) input.value = '' } </todo>
※公式サイトより引用
仮想DOM
React や Mithril と同じように仮想 DOMを採用しております。
学習コストが低い
Riot.js の API は「カスタムタグ」「コンパイラ」「オブザーバブル」「ルータ」という、概ね4つのカテゴリに分けられます。これらのAPIは全て合わせてても 20個程度しかないので非常に簡単に学ぶことができます。
ToDo アプリを作ってみる
実際に ToDo アプリを作ってみて、Riot.js の便利さを体感してみましょう。
Mithril のご紹介の時にも作りましたが、今回も以下のような ToDo アプリを作ってみようと思います。
それでは、以下のような HTML を用意します。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>ToDo App by riot.js</title> <script src="./riot+compiler.js"></script> </head> <body> <todo></todo> </body> </html>
riot+compiler.js は、公式サイト( http://riotjs.com/ja/download/ )より事前にダウンロードしておいてください。riot.js と riot+compiler.js がありますが、riot+compiler.js の方です。
カスタムタグはそのままではブラウザは解釈できないので、コンパイルが必要ですが、riot+compiler.js はユーザーがページを表示した際に自動でカスタムタグをブラウザが解釈できる JavaScript にコンパイルしてくれます。
それでは、<todo></todo> がカスタムタグです。まだカスタムタグの定義を記載していないので、このままでは何も表示されません。
以下を <body> タグに追加します。
<script src="todo.html" type="riot/tag"></script>
カスタムタグとして todo.html を読み込みます。type="riot/tag" はRiot のカスタムタグであることを表します。
それでは、todo.html を作成して、todo.html にカスタムタグを記述しましょう。
<todo> <h3>My ToDo App by riot.js</h3> <form> <input name="input"> <button>Add</button> </form> <script> this.items = []; // ToDo一覧 </script> </todo>
というわけで、こちらが todo のカスタムタグです。<h3>タグと、<form>タグ、そして<script>タグがありますね。これらはブラウザによってそのまま解釈されず、Riot.js によって JavaScript コードと 仮想 DOMにパースされて実行されます。
なお、<script>タグは省略可能ですが、わかりやすさを重視して今回は明示的に記載します。
スクリプトコードの方に、this.items というのが登場していますね。ここでの this はカスタムタグそのものを指します。正確に言うと、riot の Tag インスタンスです。今回はここに items プロパティを追加して、ここに todo タスクを1つ1つ追加していくことにしましょう。
それでは、このカスタムタグを表示してみたいと思います。index.html の下部に以下を記載します。todo をマウントすることで、<todo></todo>内にカスタムタグの内容がレンダリングされます。
<script> riot.mount('todo'); </script>
ここまでで、以下のようなフォームが表示されたと思います。
それでは、フォームに入力されたタスクを追加するイベントハンドラを作ります。todo.html に記載した todo カスタムタグを以下のように改修しましょう。
<todo> <h3>My ToDo App by riot.js</h3> <form> <input name="input" onkeyup={ this.onkeyup }> <button onclick={ this.add }>Add</button> </form> <script> this.items = []; // ToDo 一覧 this.text = ''; // 入力されたタスク名 this.onkeyup = function(e) { this.text = e.target.value }; this.add = function(e) { if (this.text) { this.items.push({ title: this.text }) this.text = this.input.value = '' } }; </script> </todo>
カスタムタグ内のHTMLにて this を参照することができます。この this は script の方でも参照されている、カスタムタグ自身のことです。script タグ内で onkeyup と add メソッドを定義したため、HTML内のイベントハンドラでも、その2つのメソッドを使うことができます。なお、この this は省略することもできますが、今回はわかりやすいように明示的に記載しました。
onkeyup はテキストに文字が入力されるたびに呼び出されるメソッドです。入力されたテキストを this.text に保存しています。
add はタスクの登録ボタンを押した時に呼びだされるメソッドです。this.items にタスクを追加した後、テキスト欄を空にしています。
これによって、Add ボタンを押下すると、タスクが追加されるようになりましたが、タスク一覧の表示を作ってないので、見た目には何も追加されてないように見えてしまいます。
次に、タスク一覧を表示させましょう。カスタムタグを以下のように改修します。
<todo> <h3>My ToDo App by riot.js</h3> <form> <input name="input" onkeyup={ this.onkeyup }> <button onclick={ this.add }>Add</button> </form> <table> <tr each={ this.items }> <td> { this.title } </td> </tr> </table> <script> this.items = []; // ToDo 一覧 this.text = ''; // 入力されたタスク名 this.onkeyup = function(e) { this.text = e.target.value }; this.add = function(e) { if (this.text) { this.items.push({ title: this.text }) this.text = this.input.value = '' } }; </script> </todo>
<table>タグを追加しました。<tr>タグ内の each 属性は Riot.js によって追加された属性です。each を使用することによってループを記述することができます。
ループ内に this.title が出現しますが、ここでの this とは、カスタムタグ自身のことではなく、items 内の要素1つを表します。ループ内では this の意味が異なることに注意してください。
ここまでで、以下のようにタスクを追加することができるようになりました。
最後に、タスクの完了/未完了を設定できるようにしましょう。todo カスタムタグを以下のように改修します。
<todo> <h3>My ToDo App by riot.js</h3> <form> <input name="input" onkeyup={ this.onkeyup }> <button onclick={ this.add }>Add</button> </form> <table> <tr each={ this.items }> <td> <input type="checkbox" checked={ this.done } onclick={ this.parent.update_is_done }> </td> <td class={ completed: this.done }> { this.title } </td> </tr> </table> <style scoped> .completed { text-decoration: line-through; } </style> <script> this.items = []; // ToDo 一覧 this.text = ''; // 入力されたタスク名 this.onkeyup = function(e) { this.text = e.target.value }; this.add = function(e) { if (this.text) { this.items.push({ title: this.text }) this.text = this.input.value = '' } }; this.update_is_done = function(e) { var item = e.item item.done = !item.done return true }; </script> </todo>
チェックボックスのHTMLを追加して、update_is_done メソッドを追加しました。
チェックボックスに this.parent というのが登場します。先ほど説明した通り、ループ内では、this の指す参照が変わり、this は this.items の要素を指します。よって要素の親であるカスタムタグ自身の update_is_done を呼び出すために this.parent.update_is_done を呼んでいます。
update_is_done メソッド内にて e.item を取得していますが、この中にはループで回している現在の要素が入ってます。e.item の done プロパティを反転させることで、タスクの完了/未完了を設定しています。
また、<style>タグを追加しました。ここで設定したcss は他のカスタムタグに影響を与えません。あくまで、todo カスタムタグに閉じてレイアウト等を設定することができます。この<style>タグはカスタムタグ内の他のHTMLと違い、Riot.js にてパースされて、<head>タグ内に自動で挿入されます。
これで、以下のような ToDo アプリになったかと思います。
まとめ
Riot.js におけるカスタムタグとは、仮想DOMとそれをレイアウトする CSS 及び実行される JavaScriptがひとまとめになったものです。これらの CSS や JavaScript はカスタムタグ内で完結しており、他のカスタムタグに影響を与えません。
これによって大規模なフロントエンドの開発においても JavaScript や CSS の追加/修正/削除による影響範囲を最小にすることができます。
なお、これらのカスタムタグは、ユーザーがページにアクセスした時に、Riot.js によって自動で xhr で読み込まれて、普通の JavaScript にコンパイルされますが、事前にコンパイルしておくことで、こうしたユーザーがページにアクセスした際の通信の発生やコンパイルによる処理を減らすことができます。
その際に Jade のような HTMLテンプレートエンジンや、CoffeeScript, ES6 等も使用することが可能です。
皆さんもぜひ、一度 Riot.js を触ってみてください!
さいの入門シリーズはコチラ