2.5.13「再帰的なコンポーネント」は、テンプレートに自分自身を入れ子にして読み込む仕組みです。基本となる構造を階層化して表現できます。「ツリー表示の例」は、コンポーネントを再帰的に用いたサンプルです。入れ子にしたデータを、ツリー構造でリスト表示してみましょう(図001)。入れ子のデータをツリー表示するデータの基本単位は、ふたつのプロパティを備えるオブジェクトにします。プロパティは、名前のテキスト(name)と、入れ子データの配列(children)です。入れ子をまとめたデータはつぎのように定数(treeData)に納め、Vueアプリケーション(demo)に渡す引数オブジェクトのdataプロパティに加えます(treeData)。アプリケーションは、HTMLドキュメントが読み込み終わったとき(DOMContentLoadedイベント)、vm.$mount()メソッドでHTMLドキュメントの指定した要素(id属性demo)に関連づけられます。=>demo.$mount('#demo'));コンポーネントを登録するVue.component()メソッドには、第1引数のid(tree-item)のほか、第2引数のオプションオブジェクトにつぎのように3つのプロパティを加えます。templateに与えたのは、あとで定めるコンポーネントのテンプレートのid属性(item-template)です。propsにはバインディングされるObject型のプロパティ(item)、computedには算出プロパティ(isFolder())を加えました。算出プロパティは、子のデータ(children)をもつかどうかブール(論理)値で返します。&&this.item.children.length;}},});<body>要素には、つぎのようにリストの大枠をつくります。親の<ul>要素にアプリケーションが関連づけされるid属性(demo)を与えました。子の要素はコンポーネント(tree-item)です。定数(treeData)に納めたデータが、プロパティ(item)にバインディングされます。ディレクティブ:は、v-bindの省略記法です。id="demo"><tree-itemclass="item":item="treeData"></tree-item></ul>コンポーネントのテンプレートは、以下のように<script>要素にtype属性をtext/x-templateとして<head>要素に書き加えてください。id属性は、前掲のVue.component()メソッドを呼び出すときオプションオブジェクトのtemplateに与えた値(item-template)です。バインディングされたプロパティ(item)のテキスト(name)を示し、子のデータ(children)の配列からオブジェクトを順に取り出します。ここでコンポーネント(tree-item)を再帰的に差し込み、v-forディレクティブで取り出したデータ(item)をバインディングすることにより、入れ子のツリー構造がつくられるのです。v-forディレクティブで配列要素を取り出すとき、ふたつ目の引数(index)にインデックスが得られます(「v-forで配列に要素をマッピングする」参照)。v-forは項目に一意のkeyを与えなければなりません(「キー付きv-for」参照)。そこで、インデックスをその値に用いました。:classはv-bind:classの省略記法で、値(算出プロパティisFolder)がtrueのときクラス(bold)を動的に割り当てます。item.children":key="index":item="child"></tree-item></ul></li></script><style>要素にはつぎのコード001のとおり、簡単なCSSを定めました。ツリーの子を開け閉じするツリー表示の子の階層を、マウスクリックで開け閉じできるようにしましょう。先に、子のデータが含まれている項目(フォルダ)に印をつけます。あとで開け閉めしますので、それがわかるように閉じていれば[+]、開いていたら[-]です。開いているどうかのプロパティ(isOpen)は、コンポーネントのdataに以下のように加えます。子のあるなしは算出プロパティ(isFolder)で確かめて、表示・非表示を切り替えるのがv-ifディレクティブです。true};},});これで、子のデータをもつ項目に印がつきます(図002)。開いているどうかのプロパティ(isOpen)はデフォルト値をtrueにしたので印ははじめ[-]です。図002■子のデータをもつ項目に印がつく子のデータをもつ項目は、クリックで開け閉じできるようにします。項目の要素(<div>)にclickイベントリスナーを、v-on(省略記法@)ディレクティブで定めます。呼び出すのはコンポーネントに以下のように加えたメソッド(toggle())です。クリックするたびに、子の階層が開いているかどうかのプロパティ(isOpen)のブール値を反転します。すると、テンプレートのリスト(<ul>要素)の表示・非表示が、v-showディレクティブにより切り替わるのです。項目が子のデータをもたなければ(isFolderがfalse)、やはりv-ifディレクティブで表示されません。!this.isOpen;}}}});これで、子のデータをもつ項目には[+]/[-]の印がつき、マウスクリックで子の階層の開け閉じができるようになりました。ここまでのコンポーネントテンプレートとJavaScriptコードを、いったんつぎのコード002にまとめます。併せて、以下のサンプル001をCodePenに掲げましたので、詳しいコードやその動きはこちらでお確かめください。項目とフォルダを加えるさらに、項目とフォルダが加えられるようにしましょう。それぞれの階層の終わりに、つぎのように追加ボタン代わりのテキスト(+)を要素(<li>)に加えます。クリックしたとき(@clickディレクティブ)にハンドラから呼び出されるのは、親アプリケーションにイベントを送るvm.$emit()メソッドで、第1引数がイベント名、第2引数は渡す値です。親はテンプレートでv-on(省略記法@)によりバインディングしたイベントから、自らのメソッド(addItem())を呼び出します。すると、子のデータ(children)の配列に新たな項目がつくられて納められるという流れです。stuff'});}}});フォルダは新たにつくるのではなく、項目をダブルクリックして変換しましょう。イベントリスナーは、つぎのようにv-on(@)にネイティブイベントdblclickを添えて定めます。以下のとおりコンポーネントに加えたのがリスナーメソッド(makeFolder())です。フォルダでないことを確かめたうえで、親アプリケーションに同じ名前のイベント(make-folder)をvm.$emit()メソッドで送ります。メソッド名はキャメルケース、イベント名はケバブケースにしてください(「イベント名」参照)。ここで気をつけなければならないのは、コンポーネント(tree-item)が入れ子になることです。したがって、親のテンプレートだけでなく、入れ子のコンポーネント(tree-item)にも、イベント(make-folder)をバインディングしなければなりません。親アプリケーションのメソッド(addItem())が、子のデータ(children)の配列に新たな項目を加えるのです。[]);this.addItem(item);},}});これで、追加記号(+)のクリックで項目が加わり、項目をダブルクリックすればフォルダに変わるようになりました。お題にした公式サイトの「ツリー表示の例」と同じ動きです。でき上がったコンポーネントテンプレートと<body>要素の記述、およびJavaScriptコードは、つぎのコード003にまとめました。以下のサンプル002をCodePenに掲げます。公式サイト「ツリー表示の例」の改訂にもとづき、コードと本文説明を修正。サンプルはCodePenに差し替えた。