このドキュメントは、Riotの公式ドキュメントのWhy Riot?とRiot developer guideの全訳です。修正あれば、こちらにプルリクエストください
追記・公式に取り込みました!
記事はこのまま置いておきますが、最新版は公式サイトを参照してください。
- 公式ドキュメント: http://riotjs.com/ja/
なぜ Riot?
答え1: カスタムタグ
RiotはIE8以降の全てのブラウザで、カスタムタグを実現します。
<todo>
<!-- layout -->
<h3>{ opts.title }</h3>
<ul>
<li each={ item, i in items }>{ item }</li>
</ul>
<form onsubmit={ add }>
<input>
<button>Add #{ items.length + 1 }</button>
</form>
<!-- logic -->
<script>
this.items = []
add(e) {
var input = e.target[0]
this.items.push(input.value)
input.value = ''
}
</script>
<todo>
カスタムタグは、関連するHTMLとJavaScriptをくっつけて再利用可能なコンポーネントとしてまとめます。React + Polymerに、"楽しい"文法と小さな学習曲線が一緒になったものをイメージしてください。
ヒューマンリーダブル
カスタムタグはHTMLで複雑なビューの構築を可能にします。あなたのアプリケーションはこんな感じになるでしょう:
<body>
<h1>Acme community</h1>
<forum-header/>
<forum-content>
<forum-threads/>
<forum-sidebar/>
</forum-content>
<forum-footer/>
<script>riot.mount('*', { api: forum_api })</script>
</body>
HTMLの文法はWebの デファクト 言語であり、ユーザインターフェースを構築するためにデザインされています。文法はシンプルで明確、入れ子構造が備わっていて、属性はカスタムタグにオプションを提供するための簡潔な方法です。
メモ タグファイルは、ブラウザで実行する前に、JavaScriptに 変換されます。
仮想DOM
- 最小のDOMの更新とリフロー
- データは一方通行: 更新とアンマウントは親から子へ伝播します。
- テンプレートは高いパフォーマンスを得るため、プリコンパイルされキャッシュされます。
- 細かい制御のためのライフサイクルイベント
- アイソモーフィックアプリケーションを実現する、カスタムタグのサーバサイドレンダリング
標準に近い
- 独自形式のイベントシステムはなし
- IE8でも使える標準化されたイベント
- レンダリングされたDOMは、自由に他のツールから操作可能
- 余計なHTMLのルート要素や
data-
属性を使う必要なし - jQueryとの親和性が高い
お気に入りのツールと一緒に
- タグファイルは、CoffeeScriptやJade、Typescript、LiveScript、ES6や、その他の好きなany pre-processorでOK。
- NPMやCommonJS、AMD、Bower、Componentが使えます。
- GulpやGrunt、Browserifyのプラグインでコンパイル
答え2: シンプルでミニマリスト
ミニマリズムが、Riotを他のライブラリと一線を画すものにしています。
楽しい文法
デザインのゴールの一つは、できる限り最小の"boilerplate"で使える、協力な文法を導入することです。
- 強力なショートカット:
class={ enabled: is_enabled, hidden: hasErrors() }
- 余計なことを考えなくてOK。
render
とかstate
、constructor
、shouldComponentUpdate
などなど。 - インターポレーション:
Add #{ items.length + 1 }
あるいはclass="item { selected: flag }"
- ロジック部分を
<script>
タグで囲むのはオプション - コンパクトなES6のメソッドの書き方
小さな学習曲線
Riotは他のURライブラリと比較して、10か100倍APIが少ないです。
- 覚えることが少ない。見なきゃいけない本もチュートリアルも少ない。
- 独自形式なものが少なく、標準的なものが多い
サイズが小さい
react.min.js – 127KB
polymer.min.js – 120KB
riot.min.js – 6.7KB
- 少ないバグ
- パースが早く、ダウンロードも容易
- エンベッダブル(組込可能): ライブラリはアプリケーション本体より小さくあるべき
- メンテナンスの手間が少ない: Riotのメンテナンスのために大きなチームを必要としない
小さくて、必要十分
Riotはモダンなクライアントサイドのアプリケーションを作るための、基本的な構成単位をすべて備えています。
- ユーザインターフェースを構築するための"Reactive"なビュー
- 分離されたモジュールのAPIを作るためのイベントライブラリ
- URLと「戻る」ボタンを処理するルータ
Riotは「オープンスタック」です。つまり、フレームワーク特有のイディオムを避けたい開発者向けです。一般的であることで、好きなデザインパターンを適用したり、混ぜたりすることができます。Facebook Fluxのようなシステムをつくることもできます。
つまり...?
Riotはサイズは小さいままに、React + Polymer + モデル + ルーティング を実現するライブラリです。今日から使えます。IE8でも。とにかくシンプルで、すごく軽い。車輪の再発明をするのではなく、これらのツールの良いとこ取りで、可能な限りシンプルにしました。
私たちは、テンプレートではなく、再利用可能なコンポーネントにフォーカスするべきです。Reactの開発者曰く:
「テンプレートは、問題ではなく、技術を分けるだけだ」
同じコンポーネントの中で、レイアウトとロジックを一緒に持てば、全体のシステムはより簡潔になります。この重要な洞察について、Reactに敬意を示したいと思います。
Riot 開発者ガイド
カスタムタグの例
Riotのカスタムタグは、ユーザインターフェースの構成要素です。 アプリケーションの「ビュー」部分を担います。Riotのいろいろな特徴をハイライトした、TODOの例の拡張から始めましょう。
<todo>
<h3>{ opts.title }</h3>
<ul>
<li each={ items }>
<label class={ completed: done }>
<input type="checkbox" checked={ done } onclick={ parent.toggle }> { title }
</label>
</li>
</ul>
<form onsubmit={ add }>
<input name="input" onkeyup={ edit }>
<button disabled={ !text }>Add #{ items.length + 1 }</button>
</form>
<script>
this.disabled = true
this.items = opts.items
edit(e) {
this.text = e.target.value
}
add(e) {
if (this.text) {
this.items.push({ title: this.text })
this.text = this.input.value = ''
}
}
toggle(e) {
var item = e.item
item.done = !item.done
return true
}
</script>
<todo>
カスタムタグはJavaScriptにコンパイルされます。
ライブデモを見て、そのソースを開くか、ZIPファイルをダウンロードします。
タグの構文
Riotのタグは、レイアウト(HTML)とロジック(JavaScript)の組み合わせです。 基本的なルールは次のとおりです。
- HTMLが先に定義され、ロジックは
<script>
タグに書かれます。<script>
タグは省略することも可能です。メモ: ドキュメントのbodyに定義を含める場合、scriptタグを必ず省略しなければなりません。scriptタグが使えるのは外部ファイルのみです。 -
<script>
タグがない場合、JavaScriptは最後のHTMLタグの直後から始まると見なされます。 - カスタムタグは、空、HTMLだけ、JavaScriptだけでも可。
- コーテーションはオプションです:
<foo bar={ baz }>
は<foo bar="{ baz }">
に変換されます。 - ES6のメソッド構文を使えます:
methodName()
はthis.methodName = function()
に変換されthis
変数は常に現在のタグのインスタンスを指します。 - クラス名のショートカット構文が使えます:
class={ completed: done }
はdone
が真のとき、class="completed"
としてレンダリングされます。 - 真偽値属性(checked, selected など)はfalsyな値の場合、無視されます:
<input checked={ undefined }>
は<input>
. - すべての属性名は 小文字 でなければなりません。
- 自己終了タグがサポートされています:
<div/>
は<div></div>
と等しくなります。 いわゆる「オープンタグ」<br>
や<hr>
、<img>
、<input>
はコンパイルの後に閉じられることはありません。 - カスタムタグは常に閉じられている必要があります。(通常通り、あるいは自己終了タグとして)
- 標準のHTMLタグ(
label
、table
、a
など)もカスタムタグ化することができますが、あまり良い手ではありません。
タグファイル内のタグ定義は常に行の先頭から書き始めます。
<!-- works -->
<my-tag>
</my-tag>
<!-- also works -->
<my-tag></my-tag>
<!-- this fails, because of indentation -->
<my-tag>
</my-tag>
インラインのタグ定義(ドキュメントのbody内)は正しくインデントされていなくてはなりません。すべてのカスタムタグは一番インデントの小さい行に揃える必要があります。タブとスペースも混ぜるべきではありません。
Scriptタグの省略
<script>
タグは省略することができます:
<todo>
<!-- layout -->
<h3>{ opts.title }</h3>
// logic comes here
this.items = [1, 2, 3]
<todo>
その場合、ロジックは最後のHTMLタグの後に開始されます。 この「オープン構文」は、Webサイト上での例示でよく使われます。
プリプロセッサ
type
属性で、プリプロセッサを指定できます。例えば次のようになります。
<script type="coffee">
# your coffeescript logic goes here
</script>
現在のところ、"coffee"と"typescript"、"es6"、"none"を使うことができます。言語指定で"text/"を接頭辞としてつけて、"text/coffee"言語指定に"text/"を接頭辞としてつけ、"text/coffee"のようにしても構いません。
詳細については プリプロセッサを参照してください。
タグのスタイリング
style
タグを含めることができます。Riot.jsは自動的にその内容を<head>
の最後に挿入します。
<todo>
<!-- layout -->
<h3>{ opts.title }</h3>
<style>
todo { display: block }
todo h3 { font-size: 120% }
/** other tag specific styles **/
</style>
<todo>
Scoped CSS
Scoped CSS も利用可能です。次の例は最初のものと等価です。
<todo>
<!-- layout -->
<h3>{ opts.title }</h3>
<style scoped>
:scope { display: block }
h3 { font-size: 120% }
/** other tag specific styles **/
</style>
<todo>
スタイルの挿入は一度だけ行われます。タグが何度使われたかは関係ありません。
Riotが挿入したCSSを上書きしたい場合、<head>
の中でCSSの挿入位置を指定することが可能です。
<style type="riot"></style>
例えば、(1)normalize.cssの後に、(2)コンポーネントライブラリのタグ内のスタイルが挿入され、(3)WebサイトのテーマCSSがデフォルトスタイルを上書きするような、ユースケースが考えられます。
タグのマウント
タグを作成したら、次のように、ページ上でそれをマウントすることができます。
<body>
<!-- place the custom tag anywhere inside the body -->
<todo></todo>
<!-- include riot.js -->
<script src="riot.min.js"></script>
<!-- include the tag -->
<script src="todo.js" type="riot/tag"></script>
<!-- mount the tag -->
<script>riot.mount('todo')</script>
</body>
ページのbody
内のカスタムタグは<todo></todo>
のように閉じられる必要があります。自己終了タグ<todo/>
はサポートしません。
マウントメソッドの使用例をいくつか示します。
// mount all custom tags on the page
riot.mount('*')
// mount an element with a specific id
riot.mount('#my-element')
// mount selected elements
riot.mount('todo, forum, comments')
文書には、同じタグの複数のインスタンスを含めることができます。
###オプション
第二引数にタグのオプションを渡すことができます。
<script>
riot.mount('todo', { title: 'My TODO app', items: [ ... ] })
</script>
渡すデータはなんでも構いません。シンプルなオブジェクトから、フルアプリケーションのAPIまで。あるいは、Fluxのストアを渡すのも手です。アーキテクチャのデザイン次第です。
次のようにタグ内のオプションは、opts
変数で参照することができます。
<my-tag>
<!-- Options in HTML -->
<h3>{ opts.title }</h3>
// Options in JavaScript
var title = opts.title
</my-tag>
ミックスイン
ミックスインは、タグを超えての機能を共有するための簡単な方法を提供します。 タグはRiotによって初期化されると、ミックスインが追加され、タグの中から使用できるようになります。
var OptsMixin = {
init: function() {
this.on('updated', function() { console.log('Updated!') })
}
getOpts: function() {
return this.opts
},
setOpts: function(opts, update) {
this.opts = opts
if(!update) {
this.update()
}
return this
}
}
<my-tag>
<h3>{ opts.title }</h3>
this.mixin(OptsMixin)
</my-tag>
この例では、どのmy-tag
タグのインスタンスに対しても、getOpts
とsetOpts
を提供するOptsMixin
ミックスインを与えています。init
は特別なメソッドで、タグに読み込まれる際にミックスインを初期化できます。(init
は、ほかのメソッドからはアクセスできません)
var my_tag_instance = riot.mount('my-tag')[0]
console.log(my_tag_instance.getOpts()) //will log out any opts that the tag has
タグは(ミックスインとして)どんなオブジェクトも受け入れます。{'key': 'val'}
、var mix = new function(...)
など。一方、それ以外の型が与えられた場合はエラーとなります。
これで、my-tag
タグの定義には、OptsMixin
に定義されたほかのものと一緒に、getId
メソッドが含まれるようになりました。
function IdMixin() {
this.getId = function() {
return this._id
}
}
var id_mixin_instance = new IdMixin()
<my-tag>
<h3>{ opts.title }</h3>
this.mixin(OptsMixin, id_mixin_instance)
</my-tag>
このようにタグ内で指定されることで、ミックスインは、単にタグの機能を拡張するだけでなく、繰り返し利用可能なインターフェイスを提供します。 タグがマウントされるたびに、内部のタグも、インスタンスはそれぞれのミックスインのコードを持つことになります。
ミックスインの共有
ミックスインをファイルやプロジェクトを超えて共有するために、riot.mixin
APIが用意されています。あなたのミックスインをグローバルに登録するには次のようにします。
riot.mixin('mixinName', mixinObject)
このミックスインをタグにロードするには、キーを指定してmixin()
メソッドを使います。
<my-tag>
<h3>{ opts.title }</h3>
this.mixin('mixinName')
</my-tag>
タグのライフサイクル
タグは次の一連の流れで作成されます。
- タグが構成される
- タグのJavaScriptロジックが実行される
- テンプレート変数が計算され、"update"イベントが発火
- ページ上でタグがマウントされ、"mount"イベントが発火
タグがマウントされた後、テンプレート変数は次のように更新されます。
- イベントハンドラが呼び出された際に自動的に(イベントハンドラ内で、
e.preventUpdate
をtrue
にセットしない場合)。例えば、最初の例のtoggle
メソッド。 -
this.update()
が現在のタグインスタンス上で呼ばれたとき -
this.update()
が親タグあるいは、さらに上流のタグで呼ばれたとき。更新は親から子への一方通行で流れる。 -
riot.update()
が呼ばれたとき。ページ上のすべてのテンプレート変数を更新。
タグが更新されるたびに、"update"イベントが発火します。
値はマウント以前に計算されるため、<img src={ src }>
という呼び出しが失敗するような心配はありません。
ライフサイクルイベント
次のような手順で、様々なライフサイクルイベントについてタグの中からリスナー登録することができます。
<todo>
this.on('mount', function() {
// right after tag is mounted on the page
})
this.on('update', function() {
// allows recalculation of context data before the update
})
this.on('unmount', function() {
// when the tag is removed from the page
})
// curious about all events ?
this.on('mount update unmount', function(eventName) {
console.info(eventName)
})
<todo>
ひとつのイベントに複数のリスナーを登録することも可能です。イベントの詳細については、observableを参照してください。
テンプレート変数 (expressions)
HTMLには、括弧で囲まれたテンプレート変数を挿入することができます。
{ /* my_expression goes here */ }
Expressions can set attributes or nested text nodes:
<h3 id={ /* attribute_expression */ }>
{ /* nested_expression */ }
</h3>
テンプレート変数は 100% JavaScript です。 いくつか例を示します:
{ title || '名称未設定' }
{ results ? '準備OK!' : '読み込み中...' }
{ new Date() }
{ message.length > 140 && 'メッセージが長すぎます' }
{ Math.round(rating) }
ゴールはテンプレート変数を小さく保ってHTMLを可能な限りクリーンに保つことです。もし、テンプレート変数が複雑になるようであれば、ロジックを"update"イベントに移すことを検討しましょう。例:
<my-tag>
<!-- the `val` is calculated below .. -->
<p>{ val }</p>
// ..on every update
this.on('update', function() {
this.val = some / complex * expression ^ here
})
</my-tag>
真偽値属性
真偽値属性 (checked, selected など) はテンプレート変数がfalse的であれば無視されます。
<input checked={ null }>
は <input>
になります。
W3Cは真偽値属性が存在する場合は、例えその値がfalse
だとしてもtrue
とするとしています。
次の書き方では、うまく動きません:
<input type="checkbox" { true ? 'checked' : ''}>
属性と値のみがテンプレート変数として許されるためです。Riotは44の異なる真偽値属性を判別します。(checked, disabled など)
クラス省略記法
RiotはCSSクラス名について特別な文法をもっています。例えば、
<p class={ foo: true, bar: 0, baz: new Date(), zorro: 'a value' }></p>
は、"foo baz zorro"として評価されます。その値が真になるプロパティ名は、クラス名のリストに追加されます。もちろん、この表記法はクラス名以外の場所で使うこともできます。もしふさわしい使い場所があれば。
括弧の表示
開始括弧をエスケープすれば、評価せずにテンプレート変数をすのまま表示することができます:
\\{ this is not evaluated \\}
outputs { this is not evaluated }
括弧のカスタマイズ
括弧を好きなものにカスタマイズするのは自由です。たとえば、このようにできます。
riot.settings.brackets = '${ }'
riot.settings.brackets = '\{\{ }}'
開始と終了はスペースで区切られています。
プリコンパイラ0 使う際は、同じく括弧オプションを設定する必要があります。
その他
style
タグの中の波括弧は、テンプレート変数として評価されません。
エスケープしないでHTMLを表示する
Riotのテンプレート変数は、HTML形式を含まないテキストのみ表示可能です。しかし、そのためのカスタムタグを作成することはできます。例えば、
<raw>
<span></span>
this.root.innerHTML = opts.content
</raw>
このようなタグを定義しておけば、他のタグの中から利用することができます。こんな感じです。
<my-tag>
<p>Here is some raw content: <raw content="{ html }"/> </p>
this.html = 'Hello, <strong>world!</strong>'
</my-tag>
警告 これはユーザをXSS攻撃の危険にさらす場合があります。信用できないソースからのデータを、絶対にロードしないようにしなくてはなりません。
入れ子のタグ
親タグ<account>
と入れ子になったタグ<subscription>
を定義しましょう:
<account>
<subscription plan={ opts.plan } show_details="true" />
</account>
<subscription>
<h3>{ opts.plan.name }</h3>
// Get JS handle to options
var plan = opts.plan,
show_details = opts.show_details
// access to the parent tag
var parent = this.parent
</subscription>
それでは、account
タグを plan
設定オプションとともに、ページにマウントします:
<body>
<account></account>
</body>
<script>
riot.mount('account', { plan: { name: 'small', term: 'monthly' } })
</script>
親タグのオプションはriot.mount
メソッドともに渡され、子タグのオプションはタグ属性として渡されます。
重要 入れ子タグは必ず親タグの中で宣言されます。ページに定義されていても初期化されません。(訳注: riot.mount
で呼んでいるのは、親タグだけだから)
入れ子のHTML
「HTMLトランスクルージョン」は、カスタムタグ内のHTMLを処理する方法のひとつです。これは、ビルトインの<yield>
タグによって実現します。次はその例です。
タグの定義
<my-tag>
<p>Hello <yield/></p>
this.text = 'world'
</my-tag>
使い方
カスタムタグはページ内に入れ子にされたHTMLとともに配置されます。
<my-tag>
<b>{ text }</b>
</my-tag>
表示結果
<my-tag>
<p>Hello <b>world</b><p>
</my-tag>
yield
の詳細については、APIドキュメントを参照してください。
##名前付き要素
name
またはid
属性のある要素は、JavaScriptから簡単にアクセスできるよう、自動的にコンテキストに追加されます。
<login>
<form id="login" onsubmit={ submit }>
<input name="username">
<input name="password">
<button name="submit">
</form>
// grab above HTML elements
var form = this.login,
username = this.username.value,
password = this.password.value,
button = this.submit
</login>
もちろん、これらの名前付き要素はHTMLの中のテンプレート変数からも参照できます: <div>{ username.value }</div>
イベントハンドラ
DOMイベントを扱う関数は「イベントハンドラ」と呼ばれます。イベントハンドラは次のように定義されます。
<login>
<form onsubmit={ submit }>
</form>
// this method is called when above form is submitted
submit(e) {
}
</login>
「on」で始まる属性(onclick
、onsubmit
、oninput
など)には、イベントが起きた際に呼ばれる関数を設定できます。この関数はテンプレート変数によって動的に定義されることも可能です。例:
<form onsubmit={ condition ? method_a : method_b }>
この関数の中でthis
は現在のタグのインスタンスを参照しています。ハンドラが呼ばれた後、this.update()
が自動的に呼ばれ、加えられた変更がUIに反映されます。
デフォルトのイベントハンドラの挙動は、チェックボックスかラジオボタンでなければ、自動的にキャンセル です。つまり、あなたのためにe.preventDefault()
は実行済みです。これはキャセルすることがほとんどだからです(しかも、忘れがち)。もし、ブラウザのデフォルトの挙動のままにしたい場合は、ハンドラでtrue
を返してください。
例えば、このsubmit
ハンドラは、実際にサーバへフォームを送信します。
submit() {
return true
}
イベントオブジェクト
イベントハンドラは通常のイベントオブジェクトを第一引数に受け取ります。次のプロパティについては、ブラウザが異なっても動作するよう標準化されています。
-
e.currentTarget
は、イベントハンドラが指定された要素を指します -
e.target
はイベントの送信元エレメントです。これは必ずしも必要ではなく、currentTarget
と同じです。 -
e.which
はキーボードイベント(keypress
、keyup
など)のキーコートです。 -
e.item
はループの中でのみ有効で、現在の要素を指します。詳しくはループを参照してください。
条件属性
条件属性を使うと、条件によって要素を表示/非表示できます。例:
<div if={ is_premium }>
<p>This is for premium users only</p>
</div>
繰り返しになりますが、テンプレート変数はシンプルなプロパティでも、フルなJavaScriptでも構いません。次の特別な属性が利用できます。
-
show
– 真のときに、style="display: ''"
として要素を表示します。 -
hide
– 真のときに、style="display: none"
として要素を非表示にします。 -
if
– ドキュメントの要素を、追加(真のとき)あるいは削除(偽のとき)します
等号には==
を使い、===
は使いません。たとえば、'a string' == true
のような書き方はOKです。
ループ
次のようにループはeach
属性として実装されています。
<todo>
<ul>
<li each={ items } class={ completed: done }>
<input type="checkbox" checked={ done }> { title }
</li>
</ul>
this.items = [
{ title: 'First item', done: true },
{ title: 'Second item' },
{ title: 'Third item' }
]
<todo>
each
属性を持った要素は配列の要素の数だけ繰り返されます。例えば、配列がpush()
、slice()
あるいはsplice
メソッドで操作された場合、自動的に新しい要素が追加/生成されます。
コンテキスト
新しいコンテキストが配列の要素ごとに作られ、その親にはparent
変数を通じてアクセスできます。例:
<todo>
<div each={ items }>
<h3>{ title }</h3>
<a onclick={ parent.remove }>Remove</a>
</div>
this.items = [ { title: 'First' }, { title: 'Second' } ]
remove(event) {
}
<todo>
ループ要素では、each
属性以外のすべては子コンテキストに紐付きます。そのため、上の例ではtitle
には直接アクセスできるのに対して、remove
はループ要素のプロパティではないため、parent.
がないとアクセスできません。
ループ要素はタグインスタンスです。Riotはもとの要素にタッチしないので、新しいプロパティが付け加えられることもありません。
ループとイベントハンドラ
イベントハンドラは配列の中の個別の要素に、event.item
でアクセスできます。では、remove
関数を実装することを考えてみます:
<todo>
<div each={ items }>
<h3>{ title }</h3>
<a onclick={ parent.remove }>Remove</a>
</div>
this.items = [ { title: 'First' }, { title: 'Second' } ]
remove(event) {
// ループ要素
var item = event.item
// 配列の中のインデックス
var index = this.items.indexOf(item)
// 配列から削除
this.items.splice(index, 1)
}
<todo>
イベントハンドラが実行された後、対象のタグインスタンスはthis.update()
を使って更新されます。(イベントハンドラの中で、e.preventUpdate
をtrue
にセットしない限り)親要素は、配列から要素が削除されたことを検知して、該当するDOM要素をドキュメントから削除します。
カスタムタグのループ
カスタムタグもループさせることができます。例:
<todo-item each="{ items }" data="{ this }"></todo-item>
現在のループ要素はthis
で参照して、ループされたタグにオプションとして渡すことができます。
非オブジェクト配列
配列の要素がオブジェクトである必要はありません。文字列や数でも構いません。そのケースでは、次のように{ name, i in items }
を使ってループにします。
<my-tag>
<p each="{ name, i in arr }">{ i }: { name }</p>
this.arr = [ true, 110, Math.random(), 'fourth']
</my-tag>
name
は要素そのものでi
がインデックス番号です。どちらも、状況に合わせてどんなラベルでも構いません。
オブジェクトのループ
オブジェクトもループにすることができます。例:
<my-tag>
<p each="{ name, value in obj }">{ name } = { value }</p>
this.obj = {
key1: 'value1',
key2: 1110.8900,
key3: Math.random()
}
</my-tag>
内部的にRiotはJSON.stringify
で変更検知をしているため、オブジェクトループは推奨されていません。オブジェクト全体として調べられ、変更が見つかると全体を再描画してしまいます。これは、動作が遅くなる原因になりえます。通常の配列は、変更箇所だけが再描画されるためもっと速いです。
標準のHTML要素にレンダリング | #riot-tag
標準HTMLも、riot-tag
属性を付けることでページ内のカスタムタグとして利用できます。
<ul riot-tag="my-tag"></ul>
このことは、CSSフレームワークとの互換性を持つ代替手段をユーザに提供しています。タグはほかのカスタムタグと同様に扱われます。
riot.mount('my-tag')
は、上に示したul
要素を、あたかも<my-tag></my-tag>
かのようにマウントします。
サーバサイドレンダリング | #server-side
Riotはサーバサイドレンダリングをサポートします。Node/io.js上で、タグをrequire
してHTMLにレンダリングすることができます:
var riot = require('riot')
var timer = require('timer.tag')
var html = riot.render(timer, { start: 42 })
console.log(html) // <timer><p>Seconds Elapsed: 42</p></timer>
ループと、条件属性がサポートされています。
アプリケーション設計
ポリシーではなく、ツール
Riotには、カスタムタグとイベントシステム(observable)、ルーターがバンドルされています。これらが、クライアントサイドアプリケーションを構築するために必要な、最も基本的な要素だと考えています。
- カスタムタグ: ユーザインターフェースのため
- イベントシステム: モジュール性のために
- ルーター: URLと「戻るボタン」のため
Riotは、厳格なルールを押しつけるよりも、創造性を発揮するためのベーシックなツールだけを提供します。この柔軟なアプローチにより、開発者に設計上の選択の余地を大きく残しています。
また、なるべくファイルサイズとAPIの数という点でも、基本要素はミニマルであるべきだと考えています。基本的なものはシンプルであるべきで、そうであれば認知的な負荷も最低限で済みます。
Observable
Observableはイベントを送ったり受け取ったりするための汎用的なツールです。依存性や「結合」を避けてモジュールを分離するために、よく使われるパターンのひとつです。イベントを使うことで、大きなプログラムは小さく簡単なユニットに分割できます。モジュールは追加することも、削除することも、アプリケーションの他の部分に影響を与えずに変更することもできます。
よくあるやりかたは、アプリケーションをひとつのコアと複数のエクステンションに分けることです。コアは何かが起きるとイベントを送ります。新しいアイテムが追加されたり、既存のアイテムが削除されたり、あるいはサーバから何かが読み込まれたり。
Observableを使うことで、エクステンションはイベントを検知して、それらに反応することができるようになります。コアが関知していなくても、コアを拡張できるわけです。これを「疎結合」と言います。
これらのエクステンションはカスタムタグ(UIコンポーネント)の場合も、非UIモジュールの場合もあります。
一度コアとイベントを注意深くデザインしてしまえば、開発チームのメンバーは他の部分に煩わされずにシステムの開発を進めることができます。
ルーティング
ルーターはURLと「戻るボタン」を扱うための、汎用的なツールです。Riotのルーターは、IE8を含むすべてのブラウザで動かすための、最小の実装になっています。次のことが可能です:
- URLのハッシュ部分を変更
- ハッシュの変更を通知
- 現在のハッシュを調べる
ルーティングのロジックはどこにでも置くことができます。カスタムタグでも、非UIモジュールでも構いません。ルーターを、各部分に指示を出すための、アプリケーションの中心的な要素と位置付けるアプリケーションフレームワークもあります。一方で、URLイベントをキーボートイベントと同じように扱って、アプリケーションの全体的なアーキテクチャを変えないというアプローチもあります。(訳注: どういった設計にするかは、開発者に委ねられています)
ロケーションバーには常にURLが表示されているわけですから、どんなブラウザアプリケーションにもルーティングは必要です。
モジュール性
カスタムタグはアプリケーションのビュー部分を作ります。モジュール化されたアプリケーションでは、これらのタグは互いについて関知せず、分離されているべきです。理想的には、外側のHTMLレイアウトにかかわらず、同じタグを別プロジェクトでも使えるはずです。
もし、2つのタグがお互いを「知って」いると、互いに依存した「密結合」を呼び込んでしまいます。そうなると、システムを壊さずにタグを自由に動かすことができなくなります。
結合を避けるには、互いを直接呼び出すよりもイベントに登録するようにします。必要なのは、riot.observable
かそれと同等のpub/subシステムです。
このイベントシステムは、シンプルにAPIから、Facebook Fluxのようなより大きな設計にまで対応できます。
Riotアプリケーションの設計例
これは、ユーザログインを実現するための、非常に簡略化したRiotアプリケーションの骨格です。
// Login API
var auth = riot.observable()
auth.login = function(params) {
$.get('/api', params, function(json) {
auth.trigger('login', json)
})
}
<!-- login view -->
<login>
<form onsubmit="{ login }">
<input name="username" type="text" placeholder="username">
<input name="password" type="password" placeholder="password">
</form>
login() {
opts.login({
username: this.username.value,
password: this.password.value
})
}
// any tag on the system can listen to login event
opts.on('login', function() {
$(body).addClass('logged')
})
</login>
そして、アプリケーションをマウントします。
<body>
<login></login>
<script>riot.mount('login', auth)</script>
</body>
上記のセットアップでは、システムは互いを知っている必要がありません。シンプルに「ログイン」イベントを検知して、それぞれに求められたことを果たします。
Observableは疎結合な(モジュール化された)アプリケーションのための、クラシックな構築要素です。