Vue.js 2.0 の render について試してみた

# はじめに :hatching_chick: 
つい先日、Vue.js 2.0 が[発表](https://medium.com/the-vue-point/announcing-vue-js-2-0-8af1bde7ab9#.3jksl1xyh)([日本語訳](http://jp.vuejs.org/2016/04/27/announcing-2.0/))されました:tada:。その発表の中で新しい機能として、`render`タグ (`render` オプション)によって自分で view のレンダリングの制御できる機能があったので、早速試してみました。

# 注意事項 :warning: 
発表されて pre-alpha なため、公式なドキュメントまだありません。今回この機能を試すにあたって、ソースコードを調べながらやっていますので、この記事はドキュメントが公開された際に、記事の説明と異なる可能性があります。また、今後の開発によっては、`render` 機能が変わる可能性があります。なので、今回の記事は、`render` というものが実際どういう風なものなのか、参考がてら読んでもらえればと思います。

# hello world :hatched_chick: 
`render` タグはどうやって使うのか？ まずは、`render` タグを使った hello world 的なもので試しながらみましょう！

テンプレート:

```html
<div id="app">
  <p>{{ hello }} <render method="render" :args="'world'"></render></p>
</div>
```

テンプレートは、単純に`hello world`と`p`タグ内で表示される、まさに hello world 的なものです。`hello` のデータバインディングによるテキスト展開と `render` タグによって構成されています。

`render` タグには、**呼び出したいレンダリング関数を指定する `method` 属性** と **その関数にパラメータとして渡したい `args` 属性**を指定する必要があります。

`method` 属性に指定できるレンダリング関数は、`methods` オプションで定義した関数です。

`args` 属性に指定できる値は、`data`オプションで定義したデータや、`props`オプションで定義したプロパティ、そして `computed` オプションで定義した computed property を指定することができます。この属性に指定する注意点としては、現状 pre-alpha では、**`v-bind`(省略記法`:`)ディレクティブ**で指定する必要があります。

このテンプレートの例では、`method` 属性は、`methods` オプションで定義した `render` 関数、`args` 属性は文字列 `world` (リテラル)を指定しています。

JavaScript 側の実装はこんな感じになります:

```js
new Vue({
  el: '#app',
  data: {
    hello: 'hello'
  },
  methods: {
    render: function (arg) {
      return arg
    }
  }
})
```

テンプレートで宣言した `render` タグで呼び出される `render` 関数は、`methods` オプションで引数を受け取る `render` 関数として定義します。`render` タグによって呼び出されるレンダリング関数は、**レンダリングする内容を返す**必要があります。こうすることによって、Vue 2.0 がこの関数からレンダリングする内容を受け取ってレンダリングするという仕組みになっています。

上記のレンダリング関数の実装(`render` 関数)では、単純に引数で渡ってきた値をそのまま返しているので、この例では、結果的には `render` タグの `args` 属性に指定した値、つまり `world` という文字列がそのままレンダリングされます。

実際には、こんな感じでレンダリングされます:

```html
<div id="app">
  <p>hello world</p>
</div>
```

`render` タグによるレンダリングどうでしたでしょうか？そんなに難しくないですよね？

なお、今回、`render` 機能を試すに当たって作成したGitHub の[リポジトリ](https://github.com/kazupon/vue-render-sandbox)を作成しました。上記の hello world な例を確認したい場合、`git clone` してきてブラウザで `hello.html` を開くと確認できるようになっていますので、動作が気になる方は確認してみてください。


# 仮想 DOM でレンダリング :baby_chick: 
先ほどの hello world の例では、単純に文字列をレンダリング関数で返してレンダリングしました。さて、`<div>...</div>` のように入れ子になった複雑なコンテンツをレンダリングしたい場合はどうやるのでしょうか？

この場合、レンダリング関数で**仮想 DOM を組み立てたものを返す**ことでレンダリングすることができます。

では、例を見てみましょう！まずは、テンプレートから:

```html
<div id="app">
  <render method="render" :args="message"></render>
</div>
```

テンプレートは、引数に `message` を受け取って `render` 関数を呼び出すように、`render` タグで宣言します。

JavaScript による実装は、以下のようにします:

```js
new Vue({
  el: '#app',
  data: {
    message: 'hello world'
  },
  methods: {
    render: function (arg) {
      return this.$createElement('div', { class: 'message' }, [ // <div>
        this.$createElement('p', {}, [arg]) // <p>
      ])
    }
  }
})
```

レンダリング関数 `render` の実装内容で、普段の Vue.js で見慣れない `$createElement` という Vue インスタンスのメソッドがでてきました。この `$createElement` はこのメソッドに**指定されたパラメータの情報を元に生成した仮想 DOM を返します。**仮想 DOM は Vue 2.0 ではタグ名、タグの属性値、タグの子要素、テキストノードなどを情報を格納したオブジェクトです。

現時点 pre-alphaで `$createElement` に指定する引数の仕様は以下になります。

- 引数1 `tag`: 生成する仮想 DOM のタグ名
- 引数2 `data`: ハッシュで構成された属性値 (省略可能)
- 引数3 `children`: 生成する仮想 DOM に内包される子要素の配列 (省略可能)
- 引数4 `namespace`: 属する名前空間 (省略可能)

上記のレンダリング関数では、`$createElement` と引数 `children` を駆使して、`p`タグを内包する`div`タグを返す 仮想 DOM を生成するように実装されており、`<div>...</div>`のような入れ子的なコンテンツをレンダリングする場合、こんな感じで仮想 DOM を組み立てて、それを返すことで、複雑なコンテンツをレンダリングすることが可能です。

なお、`p`タグの仮想 DOM を生成する `$createElement` では、引数 `children` にはレンダリング関数のパラメータ `arg` に渡ってきた値、つまり `message` の文字列が指定していますが、テキストをテキストノードとしてレンダリングしたいの場合は、このように `$createElement` を使わずとも直接文字列値を指定することでテキストノードの仮想 DOM を生成することができます。

この仮想 DOM でレンダリングする例は、以下のようにレンダリングされます。

```html
<div id="app">
  <div class="message">
    <p>hello world</p>
  </div>
</div>
```

この例は、GitHub の[ここ](https://github.com/kazupon/vue-render-sandbox/blob/master/vnode.html) (`vnode.html`) にあります。


# 子要素を持った `render` タグのレンダリング :chicken: 
`render` タグには、下記のようなタグ内部に子要素を持ったテンプレートもレンダリングすることが可能です。

テンプレート:

```html
<div id="app">
  <render method="render" :args="message">
    <ul>
      <li v-for="n in 5"></li>
    </ul>
  </render>
</div>
```

このテンプレートの `render` タグは、`v-for` ディレクティブで `li` をレンダリングするようなものを含んでいますが、このような Vue.js のディレクティブを含んだ子要素を内包するものもレンダリングすることは可能です。この場合は、**Vue によって評価されたものが仮想 DOM としてレンダリング関数の第2引数に**渡されます。

これを踏まえて、下記のように第2引数で受け取った仮想 DOM の内容を元に同関数内で動的に子要素をゴニョゴニョとレンダリングするといったことも可能になります:

```js
new Vue({
  el: '#app',
  data: {
    message: 'hello world'
  },
  methods: {
    render: function (arg, children) { // children は配列で渡ってくる
      var ul = children[0] // <ul>
      ul.children.forEach(function (li, index) {
        li.text = arg + index
      })
    return children
    }
  }
})
```

この例は、以下のようにレンダリングされます。

```html
<div id="app">
  <ul>
    <li>hello world0</li>
    <li>hello world1</li>
    <li>hello world2</li>
    <li>hello world3</li>
    <li>hello world4</li>
  </ul>
</div>
```

この例は、GitHub の[ここ](https://github.com/kazupon/vue-render-sandbox/blob/master/child.html) (`child.html`) にあります。


# コンポーネントのレンダリング :egg: 
Vue.js はコンポーネントを多用します。コンポーネントをレンダリングするには、どうやるのでしょうか？

コンポーネントのレンダリングも、`$createElement` を使用してレンダリングすることができます。

以下は、コンポーネントをレンダリングする例のテンプレート:

```html
<div id="app">
  message: <input type="text" v-model="message"></br>
  <a href="javascript:void(0);" @click="component = 'component1'">component1</a>
  <a href="javascript:void(0);" @click="component = 'component2'">component2</a>
  <render method="render" :args="component"></render>
</div>
```

この例のテンプレートは、定義したコンポーネント `component1` と `component2` を動的に切り替える例のテンプレートです。
コンポーネントの切り替えは、`render` タグと `data` オプションの `component` を使用して実現しています(コンポーネントの `is` 属性を利用すれば可能ですが、例のため、わざとこうしています)。
また、コンポーネントの props に親側から動的に値を受け渡すために、`v-model` ディレクティブが宣言された `input` タグを使用しています。

JavaScript 側の実装は、下記になります:

```js
new Vue({
  el: '#app',
  data: {
    component: 'component1',
    message: 'hello world'
  },
  components: {
    component1: {
      props: ['message'],
      data: function () {
        return { show: true }
      },
      template: '<div>'
        + '<p v-if="show">component1: {{message}}</p>'
        + '</div>'
    },
    component2: {
      props: ['message'],
      data: function () {
        return { show: true }
      },
      render: function () {
        return this.$createElement('div', {}, [
          this.$createElement('p', { directives: [{ name: 'show', value: this.show }] }, ['component2: ' + this.__toString__(this.message)])
        ])
      }
    }
  },
  methods: {
    render: function (arg) {
      return this.$createElement(arg, { props: { message: this.message } })
    }
  }
})
```

コンポーネントのレンダリングの例は、GitHub の[ここ](https://github.com/kazupon/vue-render-sandbox/blob/master/component.html) (`component.html`) にあります。

`render` 関数では、`components` オプションで定義したコンポーネント`component1` と `component2` を同関数の引数 `arg` に渡ってきた値、`data`オプションの `component` の値によって、コンポーネントの仮想 DOM を `$createElement` で生成しています。その際、コンポーネントで定義された props にデータを渡すには、`$createElement` の第2引数 `data` に `props` を指定することで渡すことが可能です。コンポーネントに渡される値が変更されるかどうかは、`input`タグのテキストを変更すれば確認できるはずなので、確認してみましょう！

この例では指定はありませんが、もちろん、第3引数 `children` に入れ子で `<div>...</div>`のようなものや、コンポーネントの仮想 DOM を指定することができます。

コンポーネント `component2`の定義している部分を見てもらえれば分かるかと思いますが、これまで、`render` によるレンダリングを説明してきましたが、`render` オプションに仮想 DOM を返すレンダリング関数を指定することも可能です。`template` オプションでテンプレートの指定がなく、`render` オプションにレンダリング関数の指定があれば、指定したレンダリング関数でレンダリングすることができます。


# まとめ :rocket: 
以上のまとめとして、`render`機能の要点をまとめます。

- `render`タグは `method`属性と `args`属性を指定する必要がある
- `method`属性には、`methods`オプションで定義した関数を指定する
- `args`属性には呼び出すレンダリング関数に渡したい値を`v-bind`で指定する
- `render`タグから呼ばれるレンダリング関数は、仮想 DOM を返す必要がある
- 仮想 DOM を生成するヘルパー的なものとして、`$createElement`インスタンスメソッドがあり、そのメソッドを利用してコンテンツを組み立てる
- `render`タグは子要素を持つことができ、それら子は、仮想 DOM としてレンダリング関数の第2引数に渡される
- コンポーネントも`$createElement`を利用してレンダリングすることができる
- `render`オプションにも仮想 DOM を返す関数を指定することでレンダリングすることが可能

# `render` について試してみた感想 :heart_eyes_cat: 
- `slot` のような子を動的にレンダリングするようなことや [vue-router](https://github.com/vuejs/vue-router) のような動的にコンポーネントを切り替えるものができそう
- 動的なコンポーネントの定義・生成ができそう
- Vue 2.0 の仮想 DOM の仕様を理解していないと辛い (JavaScript側でも宣言的に書きたいなあ。。。え、それJSXでできるって？)

とまあ、感想としてはこんな感じです。

# 最後に :heavy_exclamation_mark: 
この記事を書いて誤解されるかもしれないので、最後にコメントしておきます。

Vue 2.0 でリリースされる予定の `render` 機能ですが、これは、**テンプレートによる宣言ベースの view の開発の現状のスタイルを置き換えるものではありません。**

Vue 2.0 の発表記事にも書いてありますが、これまでどおり、[単一ファイルコンポーネント(Single File Components)](http://jp.vuejs.org/guide/application.html#単一ファイルコンポーネント) や、`template` オプションを利用した、テンプレートの宣言ベースによる開発を推奨しています。(そもそも、view の実装を全て JavaScript で開発するなんてやりたくないですよね？)

`render` 機能は、あくまでも、テンプレートの宣言ベースによるコンテンツレンダリングではできない複雑なことをしたい人のために機能として提供されているため、普通にアプリケーションを作る場合はあまり使わないでしょう。どちらかというとプラグインやサードベンダ向けのライブラリ作成者の人や、仮想 DOM 職人の人(いるのか？)にとってうれしい機能でしょう。

ただ、Vue 2.0 は、コンポーネントの実行環境であるランタイムと、テンプレートを仮想 DOM に変換するコンパイラが、モジュール化がされています。このため、事前にコンパイラでテンプレートを仮想 DOM にプリコンパイルし、それらとランタイムをプロダクション環境にアプリケーションをデプロイすることによって、レンダリングのパフォーマンスを最適化することも可能になっています。

その場合は、コンポーネントの例で少し取り上げましたが、現時点では、`render` オプションにプリコンパイルされた関数を指定する必要がありますが、今後 [vue-loader](https://github.com/vuejs/vue-loader) / [vueify](https://github.com/vuejs/vueify) のようなツールが Vue 2.0 のコンパイラに対応することによって、将来 Vue 2.0 正式リリース時には意識することがなくなるかもしれません。