Vueでv-forを使って自分でthやtdタグを出し分け可能なテーブルを作成する方法について、基本的な考え方を実例と合わせて解説しています。
最終的には以下のような表が作成できます。(グレーの色がthタグで、白色がtdタグ)

横見出しにすることもできます。

縦横見出しにすることもできます。

htmlで作成するtableの概念と構造
table作成の概念
まずは、htmlで作成するテーブルタグの概念や構造を理解しておく必要があります。
以下のように3×3の表を例として考えます。

この表の要素を分解すると、まずは1行に分けることができます。
この一塊がtrタグで囲まれた部分です。

このtrタグの中にセルが3つあります。つまり、trタグの中にtdタグが3つあることを意味しています。
あとは必要に応じて、tdタグをthタグに切り替えると、テーブルヘッド(見出し)ができます。
行の数だけ、このtrタグの要素を配列として入れます。するとテーブルの要素ができあがります。
まとめると以下のようになります。
- trタグとなる要素を用意する。値は配列で、その中にセルの要素を入れる。
- セルの要素はthタグかtdタグのどちらに振り分けるか判断できるようにする。
- 行の数だけtrタグの要素を複数入れた親要素を作る。
- v-forで親要素を回し、trタグの要素の数だけtrタグを作成する。
- 更にtrタグの要素にv-forをかけると、セルの数だけthかtdに対応したセルができる。
thタグ/tdタグとなる要素の例(1行3列の場合)
thタグ/tdタグ用のデータをは以下のようになります。
ここではテーブルのセルの入った配列のオブジェクト名を「table_cells」にしていますが、他の名前でも問題ありません。
{ "table_cells": [ {}, {}, {} ] },
↑↓ 同じ
{
"table_cells": [
{},
{},
{}
]
},
この要素をtrタグに設置したv-forで回し、中のデータに合わせてthタグかtdタグに振り分けるとセルができあがります。
テーブルセルの各要素の記述が複雑になる場合や長くなる場合など、コードの視認性を上げるために改行して記述することが一般的です。
trタグとなる要素の例(3行3列の場合)
上記の1行を3回重ねて一つの配列にすると、trタグの要素ができあがります。
ここでは、trタグの要素の名前を「rows」としていますが他の名前でも問題ありません。
rows: [
{ "table_cells": [ {}, {}, {} ] },
{ "table_cells": [ {}, {}, {} ] },
{ "table_cells": [ {}, {}, {} ] }
]
↑↓ 同じ
rows: [
{
"table_cells": [
{},
{},
{}
]
},
{
"table_cells": [
{},
{},
{}
]
},
{
"table_cells": [
{},
{},
{}
]
},
]
}
この要素をtableタグに設置したv-forで回し、要素の数だけtrタグにすると行ができあがります。
v-forを使ったtableの作成
まずはtdタグやthタグで出し分けをしないシンプルな、tdタグのみのテーブルを作成します。
コード実例
実際に、先ほど作成したtableの元となる配列をv-forで取り出すと以下のようになります。
<template>
<div>
<table>
<template v-for="tr in rows" :key="tr.index">
<tr>
<template v-for="cell in tr.table_cells" :key="cell.index">
<td>
<p></p>
</td>
</template>
</tr>
</template>
</table>
</div>
</template>
<script>
export default {
data(){
return{
rows: [
{
"table_cells": [
{},
{},
{},
]
},
{
"table_cells": [
{},
{},
{},
]
},
{
"table_cells": [
{},
{},
{},
]
},
]
}
}
}
</script>
<style lang="scss" scoped>
table{
width: 40%;
td{
border: thin solid rgba(0, 0, 0, 0.12);
}
}
</style>
v-forを使う時は v-bind:key(または:key)属性が必要です。なぜv-forにkey属性が必要なの?という疑問については下記をご参考ください。
各要素の内容
<template v-for="tr in rows" :key="tr.index">
プロパティ名rowsで渡されたtableタグのデータをtrという変数名に格納して、trタグとなる要素を1つづつ取り出す。
<template v-for="cell in tr.table_cells :key="cell.index"">
変数名trで渡されたtrタグの要素の中の、プロパティ名table_cellsを変数名cellに格納して、セルとなる要素を一つづつ取り出す。
<p></p>
セルの中身です。ここでは仮にpタグを設置しています。プロパティ名table_cellsの中のテキストに該当する部分をマスタッシュ構文で表示させるなどします。
相互に入力可能にする場合はここをinputタグに変えてv-modelとします。
<style lang="scss" scoped>
スタイルはscssでかつ、この.vueファイルのみに適用しています。
Vue CLIでscssを使う場合はsassとsass-loaderのパッケージが必要です。詳細については下記をご参考ください。
ブラウザの表示
3×3の表が表示されます。

tdタグとthタグの出し分け
次に、各セルを「見出しとなるthタグ」と「通常セルのtdタグ」の2パターンで出し分け、それぞれによって画面表示を変える処理について解説します。
td, th出し分けの考え方
td, th出し分けするためのポイントは以下の2つです。
- セルにtdかthを識別するプロパティを持たせる。
- v-if、v-else-ifを使って、tdタグかthタグにするかを出し分ける。
- CSSスタイルを設定する。
セルにtdかthを識別するプロパティを持たせる
行の先頭をthタグ、残りをtdタグにする場合は、trタグとなる要素の中のセル要素にtdとtrタグのどちらに振り分けるかを判断するためのプロパティを設定します。
ここでは仮に「cell_type
」というプロパティ名にします。
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
v-ifを使って条件分岐させるときに、cell_typeの値がTHの場合はthタグへ、TDの場合はtdタグへ振り分けます。
v-if、v-else-ifを使って、tdタグかthタグにするかを出し分ける
trタグの要素をv-forで回して、各セルを一つづつ取り出したときに、v-ifとv-else-ifを使ってcell_typeプロパティの値によって、tdタグとthタグを出し分けます。
<table>
<template v-for="tr in rows" :key="tr.index">
<tr>
<template v-for="cell in tr.table_cells">
<!--THの場合はthタグにする-->
<th v-if="cell.cell_type == 'TH'" :key="cell.index" >
<p></p>
</th>
<!--TDの場合はthタグにする-->
<td v-else-if="cell.cell_type == 'TD'" :key="cell.index">
<p></p>
</td>
</template>
</tr>
</template>
</table>
CSSスタイルを設定する
最後に、thタグとtdタグの表示を分けるためCSSを設定します。
thタグの背景色をグレー(#CCC)とする場合は以下のようになります。
<style lang="scss" scoped>
table{
width: 40%;
th,td{
border: thin solid rgba(0, 0, 0, 0.12);
}
th{
background: #ccc;
}
}
</style>
画面表示
画面の表示は以下のようになります。

先頭のセルがthタグとなり、スタイルが適用されていることがわかります。
全体のコード
<template>
<div>
<table>
<template v-for="tr in rows" :key="tr.index">
<tr>
<template v-for="cell in tr.table_cells">
<!--THの場合はthタグにする-->
<th v-if="cell.cell_type == 'TH'" :key="cell.index" >
<p></p>
</th>
<!--TDの場合はthタグにする-->
<td v-else-if="cell.cell_type == 'TD'" :key="cell.index">
<p></p>
</td>
</template>
</tr>
</template>
</table>
</div>
</template>
<script>
export default {
data(){
return{
rows: [
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
]
}
}
}
</script>
<style lang="scss" scoped>
table{
width: 40%;
th,td{
border: thin solid rgba(0, 0, 0, 0.12);
}
th{
background: #ccc;
}
}
</style>
横見出しにする
上記の例は縦見出しですが、TDとTHの指定場所を変えることで、横見出しにもできます。
ポイントは一つ目のtrタグ内の要素が全てTHになり、それ以外の要素はTDになることです。
rows: [
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TH"},
{"cell_type": "TH"},
]
},
{
"table_cells": [
{"cell_type": "TD"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
{
"table_cells": [
{"cell_type": "TD"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
]
ブラウザの表示例

コード例
<template>
<div>
<table>
<template v-for="tr in rows" :key="tr.index">
<tr>
<template v-for="cell in tr.table_cells">
<!--THの場合はthタグにする-->
<th v-if="cell.cell_type == 'TH'" :key="cell.index" >
<p></p>
</th>
<!--TDの場合はthタグにする-->
<td v-else-if="cell.cell_type == 'TD'" :key="cell.index">
<p></p>
</td>
</template>
</tr>
</template>
</table>
</div>
</template>
<script>
export default {
data(){
return{
rows: [
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TH"},
{"cell_type": "TH"},
]
},
{
"table_cells": [
{"cell_type": "TD"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
{
"table_cells": [
{"cell_type": "TD"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
]
}
}
}
</script>
<style lang="scss" scoped>
table{
width: 40%;
th,td{
border: thin solid rgba(0, 0, 0, 0.12);
}
th{
background: #ccc;
}
}
</style>
縦横見出しにする
TDとTHの指定場所を変えることで、縦と横両方の見出しにもできます。
rows: [
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TH"},
{"cell_type": "TH"},
]
},
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
]
ブラウザの表示例

コード例
<template>
<div>
<table>
<template v-for="tr in rows" :key="tr.index">
<tr>
<template v-for="cell in tr.table_cells">
<!--THの場合はthタグにする-->
<th v-if="cell.cell_type == 'TH'" :key="cell.index" >
<p></p>
</th>
<!--TDの場合はthタグにする-->
<td v-else-if="cell.cell_type == 'TD'" :key="cell.index">
<p></p>
</td>
</template>
</tr>
</template>
</table>
</div>
</template>
<script>
export default {
data(){
return{
rows: [
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TH"},
{"cell_type": "TH"},
]
},
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
{
"table_cells": [
{"cell_type": "TH"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
]
}
}
}
</script>
<style lang="scss" scoped>
table{
width: 40%;
th,td{
border: thin solid rgba(0, 0, 0, 0.12);
}
th{
background: #ccc;
}
}
</style>
実用的には、「横見出し」「縦見出し」「縦横見出し」といったボタンを用意して、ボタンクリックイベントによって、対象のセル要素のTHやTDを切り替える処理を加えます。
各セルを選択可能にする方法
v-forで作ったテーブルを単一選択や複数選択可能にする方法については下記をご参考ください。