menu
書いてる野郎
orebike@gmail.com
今はべた書きになっている HTML を Vue のテンプレートとして書き直す
現状はまったく Vue のテンプレート的記述は無く単なる HTMLである。 しかし、Vue オブジェクトの管理下には入っているので一応テンプレートとして解釈はされている。
<div id="app"> <ul> <li class="folder"> <h2>aaa</h2> <ul> <li class="file">bbb</li> <li class="file">ccc</li> <li class="file">ddd</li> </ul> </li> <li class="file">eee</li> <li class="folder"> <h2>fff</h2> <ul> <li class="file">ggg</li> <li class="folder"> <h2>hhh</h2> <ul> <li class="file">iii</li> <li class="file">jjj</li> </ul> </li> </ul> </li> <li class="file">kkk</li> </ul> </div>
この構造を Vue 側のテンプレートで順々に再現していく。
まず1階層目の4つを並べてみる。 つまり aaa, eee, fff, kkk を並べようということである。
ということでまず1階層目を v-for で置き換える
<div id="app"> <ul> <li class="folder" v-for="(item, i) in items"></li> </ul> </div>
このように v-for という属性をくっつけてその値として
(item, i) in items
という記述を入れる。
この items というのは data の中の一番の最初に出てくる items のキーの中身を指している。 ここで items の中から1つ1つ取り出しながら item(ここで勝手に決めた) という名前でハンドリングしながらループするという動きになる。i(ここで勝手に決めた)というのはループカウンタで1から順番にループする度に増えるということになる。
そして、ブラウザで開いてみると、コード上は li は1個しか書いてないのだが、 画面要素としてはこのように 4つ でてきて、データの数と一致していることがわかる。 これは HTML コードを Vue オブジェクトが読み取って解釈し画面に再描画しているからこうなる。
<ul> <li class="folder"></li> <li class="folder"></li> <li class="folder"></li> <li class="folder"></li> </ul>
しかし、今は単にループしているだけなので名前はないし、class も合ってない。 現状全部 folder になってしまっているのでこいつを正しく反映させよう。
<div id="app"> <ul> <li v-for="(item, i) in items" v-bind:class="item.type"></li> </ul> </div>
v-bind:class という属性をつけてその値を item.type としている。
この v-bind というのはこの属性に対してはこのデータを採用しますという記述である。
<hoge v-bind:属性名="採用するデータ">
この場合ならば class 属性に item.type を使うよということである。この item というのは v-for で勝手に決めた名前である。つまり、items の配下のデータの中の type という値を使うぜということになる。
実行してみると、正しく反映されたことがわかる。
<ul> <li class="folder"></li> <li class="file"></li> <li class="folder"></li> <li class="file"></li> </ul>
タグの中身が空っぽなのでそれを入れ込む。 2重の中括弧の中に v-bind の属性値と似たような形で書く。属性値と違って、タグの中身のテキストはこのように書く
<div id="app"> <ul> <li v-for="(item, i) in items" v-bind:class="item.type" > {{ item.name }} </li> </ul> </div>
これだからといってこのようには書けないので注意
<li v-for="(item, i) in items" class="{{item.type}}" ></li>
フォルダの場合だけ名前は h2 でマークアップされるので分岐を入れておく
<li v-for="(item, i) in items" v-bind:class="item.type"> <template v-if="item.type === 'folder'"> <h2>{{ item.name }}</h2> </template> <template v-else> {{ item.name }} </template>
v-if という属性を指定して、その値に条件を指定してその条件が真の場合のみ中の要素を描画するようにしている。
v-else はその逆である。
このようにタグを切り替えるのではなくタグ自体を表示したくない場合は構文に使えるダミー的タグである template を使うとヨイ
そうすると、このような HTML として描画される。
<ul> <li class="folder"> <h2>aaa</h2> </li> <li class="file">eee</li> <li class="folder"> <h2>fff</h2> </li> <li class="file">kkk</li> </ul>
それではこれに従ってさらに下の階層を描画するように記述していく
<ul> <li v-for="(item, i) in items" v-bind:class="item.type"> <template v-if="item.type === 'folder'"> <h2>{{ item.name }}</h2> <ul> <li v-for="(item, j) in item.items"> <template v-if="item.type === 'folder'"> <h2>{{ item.name }}</h2> <ul> <li v-for="(item, k) in item.items"> <template v-if="item.type === 'folder'"> <h2>{{ item.name }}</h2> <ul> <li v-for="(item, l) in item.items">{{ item.name }}</li> </ul> </template> <template v-else>{{ item.name }}</template> </li> </ul> </template> <template v-else>{{ item.name }}</template> </li> </ul> </template> <template v-else>{{ item.name }}</template> </li> </ul>
フォルダが2階層しかない、根っこの階層まで入れると3階層である。 なので、ここでは3重ループを記述している。 しかしフォルダというのはどれだけでも階層がある可能性がある。
つまり、1つのテンプレートで書くことができないのである。 1個のテンプレートではこのような再帰構造は設定できないので、とりあえず今は3段までできたのでこれでよしとしておく。
ループ構造はどれも同じであるが、ポイントとしてはここである
<li v-for="(item, k) in item.items">
下位の v-for のタグはこのようになっており、勝手につけた item という名前が上位階層の名前と被っているのである。
しかしこれはこれで動作させると普通に動作する。 v-for でつけるループ変数の名前はその要素内で閉じていて上位のループ変数と名前が被っても問題無いことになっている。 しかし、それと引き換えに上位の値を内部で使えないということになっている。
実行してみると試作で作った HTML と同様の構造の HTML が描画されているはずである。
これまでのまとめ
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Hello</title>
<script src="https://unpkg.com/vue@latest"></script>
<script src="https://unpkg.com/vuex@latest"></script>
</head>
<body>
<div id="app">
<!-- ■■■■■■■■ 今回はココ ■■■■■■■■ -->
<ul>
<li v-for="(item, i) in items" v-bind:class="item.type">
<template v-if="item.type === 'folder'">
<h2>{{ item.name }}</h2>
<ul>
<li v-for="(item, j) in item.items">
<template v-if="item.type === 'folder'">
<h2>{{ item.name }}</h2>
<ul>
<li v-for="(item, k) in item.items">
<template v-if="item.type === 'folder'">
<h2>{{ item.name }}</h2>
<ul>
<li v-for="(item, l) in item.items">{{ item.name }}</li>
</ul>
</template>
<template v-else>{{ item.name }}</template>
</li>
</ul>
</template>
<template v-else>{{ item.name }}</template>
</li>
</ul>
</template>
<template v-else>{{ item.name }}</template>
</li>
</ul>
</div>
<script>
var data = {
id: 1, name: "", type: "folder",
items: [
{
id: 2, name: "aaa", type: "folder",
items: [
{ id: 3, name: "bbb", type: "file" },
{ id: 4, name: "ccc", type: "file" },
{ id: 5, name: "ddd", type: "file" }
]
},
{ id: 6, name: "eee", type: "file" },
{
id: 7, name: "fff", type: "folder",
items: [
{ id: 8, name: "ggg", type: "file" },
{
id: 9, name: "hhh", type: "folder",
items: [
{ id: 10, name: "iii", type: "file" },
{ id: 11, name: "jjj", type: "file" }
]
}
]
},
{ id: 12, name: "kkk", type: "file" }
]
};
var vm = new Vue({
el: "#app",
data: data
});
</script>
</body>
</html>