カスタムタグ

カスタムタグの例

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 ref="input" onkeyup={ edit }>
    <button disabled={ !text }>Add #{ items.length + 1 }</button>
  </form>

  <script>
    this.items = opts.items

    edit(e) {
      this.text = e.target.value
    }

    add(e) {
      e.preventDefault()
      if (this.text) {
        this.items.push({ title: this.text })
        this.text = this.refs.input.value = ''
      }
    }

    toggle(e) {
      var item = e.item
      item.done = !item.done
    }
  </script>

</todo>

カスタムタグはJavaScriptにコンパイルされます

ライブデモを見て、そのソースを開くか、ZIPファイルをダウンロードします。

タグの構文

Riotのタグは、レイアウト(HTML)とロジック(JavaScript)の組み合わせです。 基本的なルールは次のとおりです。

インラインのタグ定義(ドキュメントのbody内)は正しくインデントされているべきです。すべてのカスタムタグは一番インデントの小さい行に揃える必要があります。タブとスペースも混ぜるべきではありません。

scriptタグの省略

<script>タグは省略することができます:

<todo>

  <!-- layout -->
  <h3>{ opts.title }</h3>

  // logic comes here
  this.items = [1, 2, 3]

</todo>

その場合、ロジックは最後のHTMLタグの後に開始されます。 この「オープン構文」は、Webサイト上での例示でよく使われます。

プリプロセッサ

type属性で、プリプロセッサを指定できます。例えば次のようになります。

<my-tag>
  <script type="coffee">
    # your coffeescript logic goes here
  </script>
</my-tag>

現在のところ、”coffee”と”typescript”、”es6”、”none”を使うことができます。言語指定に”text/”を接頭辞としてつけ、”text/coffee”のようにしても構いません。

詳細については プリプロセッサを参照してください。

タグのスタイリング

styleタグを含めることができます。Riot.jsは自動的にその内容を<head>の最後に挿入します。これは、タグが何回使用されても、挿入は1度だけ行われます。

<todo>

  <!-- layout -->
  <h3>{ opts.title }</h3>

  <style>
    h3 { font-size: 120% }
    /** 他のタグ固有のスタイル **/
  </style>

</todo>

Scoped CSS

Scoped css と :scope 擬似クラス もすべてのブラウザで利用可能です。Riot.jsにはJSによる独自のカスタム実装があり、ブラウザの実装には依存せず、フォールバックもしません。次の例は最初のものと等価です。以下の例では、スタイルをスコープ化するためにタグの名前を使うのではなく、 :scope 擬似クラスを使用しています。

<todo>

  <!-- layout -->
  <h3>{ opts.title }</h3>

  <style>
    :scope { display: block }
    h3 { font-size: 120% }
    /** 他のタグ固有のスタイル **/
  </style>

</todo>

Riotが挿入したCSSを上書きしたい場合、<head>の中でCSSの挿入位置を指定することが可能です。

<style type="riot"></style>

例えば、(1)normalize.cssの後に、(2)コンポーネントライブラリのタグ内のスタイルが挿入され、(3)WebサイトのテーマCSSがデフォルトスタイルを上書きするような、ユースケースが考えられます。

タグのマウント

タグを作成したら、次のように、ページ上でそれをマウントすることができます:

<body>

  <!-- カスタムタグをbodyの任意の場所に配置 -->
  <todo></todo>

  <!-- riot.jsのインクルード -->
  <script src="riot.min.js"></script>

  <!-- riotコンパイラがjavascriptに変換したタグをインクルード -->
  <script src="todo.js"></script>

  <!-- タグのマウント -->
  <script>riot.mount('todo')</script>

</body>

ページのbody内のカスタムタグは<todo></todo>のように閉じられる必要があります。自己終了タグ<todo/>はサポートしません。

マウントメソッドの使用例をいくつか示します。

// ページ上の全てのカスタムタグのマウント
riot.mount('*')

// 特定のid付きの、ある要素をマウント
riot.mount('#my-element')

// 選択した要素をマウント
riot.mount('todo, forum, comments')

文書には、同じタグの複数のインスタンスを含めることができます。

DOM要素へのアクセス

Riotは、this.refsオブジェクトに続くref属性を持つ要素へのアクセスと、豊富なショートハンドとしてif="{...}"属性のようなプロパティメソッドを提供します。 しかし、時によっては、あなたはこれらの組み込みの機能とは相性の良くないHTMLの要素を参照したり、操作したりする必要に迫られるでしょう。

jQueryやZepto、querySelectorなどをどのように使うべきか

もし、Riotの中でDOMにアクセスする必要が生じたならば、 あなたはタグのライフサイクル を見てみたいと思うでしょう。そして、 mountイベントが最初に発火するまで、DOM要素が生成されないことに気づくかもしれません。これはつまり、それより前の、要素を選択しようとするあらゆる試みが失敗することを意味します。

<example-tag>
  <p id="findMe">Do I even Exist?</p>

  <script>
  var test1 = document.getElementById('findMe')
  console.log('test1', test1)  // 失敗

  this.on('update', function(){
    var test2 = document.getElementById('findMe')
    console.log('test2', test2) // 成功すると、更新毎に発火
  })

  this.on('mount', function(){
    var test3 = document.getElementById('findMe')
    console.log('test3', test3) // 成功すると、一度発火(マウント毎に)
  })
  </script>
</example-tag>

コンテキスト依存のDOMクエリ

さて、私たちは今、updateまたはmountイベントを待つことによって、DOM要素を得る方法を知っています。さらに、要素のコンテキストをroot element(私たちが作成しているRiotタグ)へのクエリに追加することによって、このやり方をさらに便利にすることができます。

<example-tag>
  <p id="findMe">Do I even Exist?</p>
  <p>Is this real life?</p>
  <p>Or just fantasy?</p>

  <script>
  this.on('mount', function(){
    // コンテキストはjQuery
    $('p', this.root)

    // コンテキストはクエリセレクタ
    this.root.querySelectorAll('p')
  })
  </script>
</example-tag>

オプション

第二引数にタグのオプションを渡すことができます。

<script>
riot.mount('todo', { title: 'My TODO app', items: [ ... ] })
</script>

渡すデータはなんでも構いません。シンプルなオブジェクトから、フルアプリケーションのAPIまで。あるいは、Fluxのストアを渡すのも手です。アーキテクチャのデザイン次第です。

次のようにタグ内のオプションは、opts変数で参照することができます。

<my-tag>

  <!-- Options in HTML -->
  <h3>{ opts.title }</h3>

  // JavaScriptのオプション
  var title = opts.title

</my-tag>

タグのライフサイクル

タグは次の一連の流れで作成されます。

  1. タグが構成される
  2. タグのJavaScriptロジックが実行される
  3. テンプレート変数が計算される
  4. ページ上でタグがマウントされ、”mount”イベントが発火

タグがマウントされた後、テンプレート変数は次のように更新されます。

  1. イベントハンドラが呼び出された際に自動的に(イベントハンドラ内で、e.preventUpdatetrueにセットしない場合)。例えば、最初の例のtoggleメソッド。
  2. this.update()が現在のタグインスタンス上で呼ばれたとき
  3. this.update()が親タグあるいは、さらに上流のタグで呼ばれたとき。更新は親から子への一方通行で流れる。
  4. riot.update()が呼ばれたとき。ページ上のすべてのテンプレート変数を更新。

タグが更新されるたびに、”update”イベントが発火します。

値はマウント以前に計算されるため、<img src={ src }>という呼び出しが失敗するような心配はありません。

ライフサイクルイベント

次のような手順で、様々なライフサイクルイベントについてタグの中からリスナー登録することができます。

<todo>

  this.on('before-mount', function() {
    // タグがマウントされる前
  })

  this.on('mount', function() {
    // タグがページにマウントされた直後
  })

  this.on('update', function() {
    // 更新前のコンテキストデータの再計算が可能
  })

  this.on('updated', function() {
    // updateの呼び出し後にタグテンプレートが更新された直後
  })

  this.on('before-unmount', function() {
    // タグが解除される前
  })

  this.on('unmount', function() {
    // タグがページから解除される前
  })

  // 全てのイベントについて注目する?
  this.on('*', function(eventName) {
    console.info(eventName)
  })

</todo>

ひとつのイベントに複数のリスナーを登録することも可能です。イベントの詳細については、observableを参照してください。

ミックスイン

ミックスインは、タグを超えての機能を共有するための簡単な方法を提供します。 タグはRiotによって初期化されると、ミックスインが追加され、タグの中から使用できるようになります。

var OptsMixin = {
  // 引数`opts`はタグによって受け取られるオプションのオブジェクト
  init: function(opts) {
    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>
  <h1>{ opts.title }</h1>

  this.mixin(OptsMixin)
</my-tag>

この例では、どのmy-tagタグのインスタンスに対しても、getOptssetOptsを提供するOptsMixinミックスインを与えています。initは特別なメソッドで、タグに読み込まれる際にミックスインを初期化できます。(initは、ほかのメソッドからはアクセスできません)

var my_tag_instance = riot.mount('my-tag')[0]

console.log(my_tag_instance.getOpts()) // タグが持つ全てのoptsをログアウトする

タグは(ミックスインとして)どんなオブジェクトも受け入れます。{'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>
  <h1>{ opts.title }</h1>

  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>

グローバルなミックスイン

もしすべてのタグに機能を追加する必要がある場合は、次のようにグローバルなミックスインを登録することができます

// タグをマウントする前に登録する必要がある
riot.mixin(mixinObject)

共有されたミックスインとは異なり、グローバルのミックスインは自動的にすべてのマウントされたタグから呼び出されます。注意して使ってください!

riot.mixin('globalMixinOne', mixinObjectOne, true)
console.log(riot.mixin('globalMixinOne') === mixinObjectOne) // true

ミックスインのオブジェクトを取得する必要が出てきた場合、代わりにグローバルなミックスインオブジェクトを名前で設定することもできます。その場合、3番目の_boolean_パラメータは、このミックスインが共有されたものではなく、グローバルなミックスインであることを示します。

テンプレート変数 (expressions)

HTMLには、括弧で囲まれたテンプレート変数を挿入することができます。

{ /* ここにmy_expressionが表示される */ }

テンプレート変数は属性かネストされたテキストに使えます。

<h3 id={ /* attribute_expression */ }>
  { /* nested_expression */ }
</h3>

テンプレート変数は 100% JavaScript です。 いくつか例を示します:

{ title || '名称未設定' }
{ results ? '準備OK!' : '読み込み中...' }
{ new Date() }
{ message.length > 140 && 'メッセージが長すぎます' }
{ Math.round(rating) }

ゴールはテンプレート変数を小さく保ってHTMLを可能な限りクリーンに保つことです。もし、テンプレート変数が複雑になるようであれば、ロジックを”update”イベントに移すことを検討しましょう。例:

<my-tag>

  <!-- `val`は以下のように計算される -->
  <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”として評価されます。その値が真になるプロパティ名は、クラス名のリストに追加されます。もちろん、この表記法はクラス名以外の場所で使うこともできます。もしふさわしい使い場所があれば。

クラスのオブジェクト記法

>=3.4.0

DOMエレメントのCSSクラス名指定に、オブジェクトを使うこともできます。例えば:

<my-tag>
  <p class={ classes }></p>
  <script>
    hasAnimation() {
      return true
    }

    this.randomNumber = 5

    this.classes = {
      foo: true,
      bar: false,
      number: '3 > randomNumber',
      animated: 'hasAnimation()', // 注意!メソッドは文字列で指定すること
      baz: new Date(),
      zorro: 'a value'
    }
  </script>
</my-tag>

とすれば、foo number animated baz zorroと評価されます。Riotは与えられたオブジェクトのうち、値がtruthfulな(真として評価される)キー名を、すべて(CSSクラス名の)文字列として出力します。

インラインスタイルのオブジェクト記法

>=3.4.0

インラインの style 属性をオブジェクトの形で与えることができ、Riotはそれを自動的に(インラインスタイル指定の)文字列に変換します。例えば:

<my-tag>
  <p style={ styles }></p>
  <script>
    this.styles = {
      color: 'red',
      height: '10rem'
    }
  </script>
</my-tag>

とすれば、<p style="color: red; height: 10rem"></p>と評価されます。

括弧の表示

開始括弧をエスケープすれば、評価せずにテンプレート変数をすのまま表示することができます:

\\{ this is not evaluated \\}{ this is not evaluated }と出力されます

評価されるべきではない場合、常にカッコはエスケープしてください。例えば、以下のような正規表現パターンは意図した入力(任意の2つの数字)を検証するのに失敗し、代わりにそれに続くただ1つの数字「2」のみ受け入れます。

<my-tag>
  <input type='text' pattern="\d{2}">
</my-tag>

正しい実装はこのようになるでしょう:

<my-tag>
  <input type='text' pattern="\d\{2}">
</my-tag>

括弧のカスタマイズ

括弧を好きなものにカスタマイズするのは自由です。たとえば、このようにできます。

riot.settings.brackets = '${ }'
riot.settings.brackets = '\{\{ }}'

開始と終了はスペースで区切られています。

プリコンパイラを使う際は、同じく括弧オプションを設定する必要があります。

その他

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>

jsfiddle上のデモ

警告 これはユーザをXSS攻撃の危険にさらす場合があります。信用できないソースからのデータを、絶対にロードしないようにしなくてはなりません。

メモ: テンプレート(<span></span>)では更新に対応できないのでより実践的な状況では、updateイベントを指定する必要があります。 テンプレート(<span></span)内で更新する式が存在しないためです。

<raw>
  <span></span>

  this.innerHTML.root = opts.content
  this.on('update', function(){ this.root.innerHTML = opts.content });
</raw>

jsfiddle上のデモ

入れ子のタグ

親タグ<account>と入れ子になったタグ<subscription>を定義しましょう:

<account>
  <subscription  plan={ opts.plan } show_details="true" />
</account>


<subscription>
  <h3>{ opts.plan.name }</h3>

  // JSの処理をオプションにする
  var plan = opts.plan,
      show_details = opts.show_details

  // 親タグにアクセスする
  var parent = this.parent

</subscription>

重要 ブラウザの仕様上大文字は小文字に自動的に変換されるため、キャメルケースではなくてアンダースコアを使用しshow_detailsと名付けています。

それでは、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ドキュメントを参照してください。

名前付き要素

ref属性を持つ要素は this.refs の配下のコンテキストに自動的にリンクされるので、JavaScriptで簡単にアクセスできます

<login>
  <form ref="login" onsubmit={ submit }>
    <input ref="username">
    <input ref="password">
    <button ref="submit">
  </form>

  // 上のHTML要素をつかむ
  submit(e) {
    var form = this.refs.login,
        username = this.refs.username.value,
        password = this.refs.password.value,
        button = this.refs.submit
  }

</login>

マウントイベントが発火するとrefs属性が設定され、’mount’(this.on('mount', function(){...}))または他のイベントハンドラ内の this.refs コレクションにアクセスできます。

>=3.0

もし ref 属性がRiotタグに適用されているならば、上記の場合と同様に、DOM要素ではなくタグのインスタンスが参照されます。例:

<my-tag>
  <my-nested-tag data-ref="one"></my-nested-tag>
  <div data-ref="two"></div>  

  this.on('mount', function() {
    console.log(this.refs.one); // Riotタグオブジェクト
    console.log(this.refs.two); // HTMLのDOM要素
  });
</my-tag>

同じ ref の値が複数の要素に使われているような場合、refs プロパティはそれぞれの要素 / タグの配列を返します。例:

<my-tag>
  <div data-ref="items" id="alpha"></div>
  <div data-ref="items" id="beta"></div>

  this.on('mount', function() {
    console.log(this.refs.items); // [div#alpha, div#beta]
  });
</my-tag>

イベントハンドラ

DOMイベントを扱う関数は「イベントハンドラ」と呼ばれます。イベントハンドラは次のように定義されます。

<login>
  <form onsubmit={ submit }>

  </form>

  // このメソッドは上記のフォームがサブミットされたときに呼ばれる
  submit(e) {

  }
</login>

「on」で始まる属性(onclickonsubmitoninputなど)には、イベントが起きた際に呼ばれる関数を設定できます。この関数はテンプレート変数によって動的に定義されることも可能です。例:

<form onsubmit={ condition ? method_a : method_b }>

関数 thisは、現在のタグインスタンスを参照します。ハンドラが呼び出された後、this.update()が自動的に呼び出され、加えられたすべての変更がUIに反映されます。

イベントオブジェクト

イベントハンドラは通常のイベントオブジェクトを第一引数に受け取ります。次のプロパティについては、ブラウザが異なっても動作するよう標準化されています。

条件属性

条件属性を使うと、条件によって要素を表示/非表示できます。例:

<div if={ is_premium }>
  <p>This is for premium users only</p>
</div>

繰り返しになりますが、テンプレート変数はシンプルなプロパティでも、フルなJavaScriptでも構いません。次の特別な属性が利用できます。

等号には==を使い、===は使いません。たとえば、'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メソッドで操作された場合、自動的に新しい要素が追加/生成されます。

コンテキスト

各項目に対して新しいコンテキストが作成されます。これらはタグのインスタンスです。ループがネスとされると、ループ内の全ての子タグは、親のループのプロパティとメソッドのいずれかを継承しますが、彼ら自身は未定義(undefined)です。このように、Riotは親タグによって上書きされるべきではないものを上書きするのを避けます。

親は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.preventUpdatetrueにセットしない限り)親要素は、配列から要素が削除されたことを検知して、該当する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="{ value, name in obj }">{ name } = { value }</p>

  this.obj = {
    key1: 'value1',
    key2: 1110.8900,
    key3: Math.random()
  }
</my-tag>

内部的にRiotはJSON.stringifyで変更検知をしているため、オブジェクトループは推奨されていません。オブジェクト全体として調べられ、変更が見つかると全体を再描画してしまいます。これは、動作が遅くなる原因になりえます。通常の配列は、変更箇所だけが再描画されるためもっと速いです。

ループ使用のさらなるヒント

パフォーマンス

デフォルトのeachディレクティブのアルゴリズムは、indexOfの結合を通して、ループされたDOMノードの位置をコレクション内の要素と同期させます。この戦略は、大量のデータを扱う場合には効率的ではなないかもしれません。その場合、ループしたタグの順序を変える必要はなく、no-reorderオプションを追加できるので、テンプレートを更新するだけで良いです。

<loop>
  <!-- `items` here might be a huge collection of data... -->
  <table>
    <tr each="{ item in items }" no-reorder>
      <td>
        { item.name }
      </td>
      <td>
        { item.surname }
      </td>
    </tr>
  </table>
</loop>

上記例のtableの行は、最初にバインドされたitemの位置に続いて並べ替えることなく追加/削除/更新されます。

キー(Key)

>= v3.7

ループされたタグにkey属性を追加すると、項目の位置を追跡することより、もっと正確な戦略が提供されます。これにより、コレクションが不変の場合、ループのパフォーマンスが大幅に向上します。

<loop>
  <ul>
    <li each={ user in users } key="id">{ user.name }</li>
  </ul>
  <script>
    this.users = [
      { name: 'Gian', id: 0 },
      { name: 'Dan', id: 1 },
      { name: 'Teo', id: 2 }
    ]
  </script>
</loop>

key属性はテンプレート変数でも生成できます

<loop>
  <ul>
    <li each={ user in users } key={ user.id() }>{ user.name }</li>
  </ul>
  <script>
    this.users = [
      { name: 'Gian', id() { return 0 } },
      { name: 'Dan', id() { return 1 } },
      { name: 'Teo', id() { return 2 } }
    ]
  </script>
</loop>

virtualタグ

特定のタグに囲まれないループをしたい場合は<virtual>タグが使えます。ループ後に消滅し、内部のHTMLのみがレンダリングされます。例:

<dl>
  <virtual each={item in items}>
    <dt>{item.key}</dt>
    <dd>{item.value}</dd>
  </virtual>
</dl>

しかし、 virtualはループに対して排他的ではなく、任意のタグに対してifと組み合わせて使うことができます

<virtual data-is="my-tag" if={condition}>
  <p>Show me with no wrapper on condition</p>
</virtual>

標準のHTML要素にレンダリング

標準HTMLも、data-is属性を付けることでページ内のカスタムタグとして利用できます。

<ul data-is="my-list"></ul>

このことは、CSSフレームワークとの互換性を持つ代替手段をユーザに提供しています。タグはほかのカスタムタグと同様に扱われます。

riot.mount('my-list')

これは、上に示したul要素を、あたかも<my-list></my-list>かのようにマウントします。

メモ: data-is属性の式も使うことができ、riotは同じDOMノード上の異なるタグを動的にレンダリングできることに注意してください

<my-tag>
  <!-- dynamic component -->
  <div data-is={ component }></div>
  <button onclick={ switchComponent }>
    Switch
  </button>

  <script>
    this.component = 'foo'

    switchComponent() {
      // riot will render the <bar> component
      // replacing <foo>
      this.component = 'bar'
    }
  </script>
</my-tag>

メモ: data-is 属性を使うならば、タグがどのように定義されていかにかかわらず、タグ名は全て小文字にすべきです。

<MyTag></MyTag> <!-- 正しい -->
<div data-is="mytag"></div> <!-- これも正しい -->
<div data-is="MyTag"></div> <!-- 正しくない -->
<script type="text/javascript">
  riot.mount('MyTag');
</script>

サーバサイドレンダリング

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のDOMの取り扱いにおける注意事項

Riotタグはブラウザのレンダリング処理に依存しているため、特定の状況では、作成したコンポーネント(訳注: Riotタグのこと)のテンプレート記述が正しくレンダリングされないことに注意しましょう。

次のRiotタグを考えてみましょう:


<my-fancy-options>
  <option>foo</option>
  <option>bar</option>
</my-fancy-options>

このマークアップは、<select>タグに差し込まれなければ正しいHTMLにはなりませんが、その際に気を付けなければならないことがあります:


<!-- こちらは間違った記述。selectタグの子要素には<option>しか認められていないためです。 -->
<select>
  <my-fancy-options />
</select>

<!-- こちらが正しい記述。こうすることでRiotは、<select>を<my-fancy-options>の「ルート」ノード とみなし、<option>タグを出力します -->
<select data-is='my-fancy-options'></select>

table, select, svg...といったタグは、カスタムタグを子要素にすることを認めていません。そのため、Riotのカスタムタグ(<virtual>であっても)の使用も禁止です。代わりに、上の例のようにdata-isを使いましょう。この件の詳細は、こちらのイシューをご確認ください(英語)