【概要】
Element PlusのExpandable rowのサンプルコードを見てスロットを使用している<template #default="props">
配下のデータがどのように渡されているのか気になりました。
(そもそもスロット自体の理解もあいまいだったので、スロットの勉強も含めて理解したいと思いました)
そこで実装コードを読んで超ざっくりと理解できたことを、超ざっくりと解説します。
【環境】
Element Plus 2.3.14
【説明用コード】
こちらのコードをもとに説明していきます。
上記リンク先にある同じコード
<template>
<el-table :data="tableData">
<el-table-column type="expand">
<template #default="props">
<div>
<p>年齢: {{ props.row.age }}</p>
<p>性別: {{ props.row.gender }}</p>
</div>
</template>
</el-table-column>
<el-table-column label="ユーザid" prop="id" />
<el-table-column label="名前" prop="name" />
</el-table>
</template>
<script lang="ts" setup>
const tableData = [
{
id: 1,
name: '佐藤',
age: 20,
gender: '男',
},
]
</script>
【ローカル開発手順】
(VSCodeだとpnpm
コマンドを使うには、PowerShellではなくGit Bashを利用する必要がありました)
【コード解説】
コードは必要箇所を抜粋しています。
1. el-table
まず前提としてel-table
やel-table-column
というタグはvueのコンポーネントです。
とりあえず一番の親であるel-table
のコードを見てみます。
<slot />
があるので、ここにel-table-column
配下の要素が入りそうです。
しかし、レンダリングされた画面ではこの<slot />
で宣言された箇所には要素が入っていません。
では、どこにレンダリングされるのでしょうか?
<div ref="hiddenColumns" class="hidden-columns">
<slot />
</div>
<slot />
で宣言された箇所には中身のないdiv
が3つあるだけです。
年齢・性別の拡張表示要素はtbody
配下にレンダリングされているのが確認できます。
tbody
を表示しているのは下記箇所です。
<table-body
:context="context"
:highlight="highlightCurrentRow"
:row-class-name="rowClassName"
:tooltip-effect="tooltipEffect"
:tooltip-options="tooltipOptions"
:row-style="rowStyle"
:store="store"
:stripe="stripe"
/>
2. table-body
table-body
の実装を見ると、render
関数でwrappedRowRender
の結果を返しています。
では、wrappedRowRender
の定義を確認してみましょう。
render() {
const { wrappedRowRender, store } = this
const data = store.states.data.value || []
// Why do we need tabIndex: -1 ?
// If you set the tabindex attribute on an element ,
// then its child content cannot be scrolled with the arrow keys,
// unless you set tabindex on the content too
// See https://github.com/facebook/react/issues/25462#issuecomment-1274775248 or https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/tabindex
return h('tbody', { tabIndex: -1 }, [
data.reduce((acc: VNode[], row) => {
return acc.concat(wrappedRowRender(row, acc.length))
}, []),
])
},
useRender
の結果を使っています。
では、useRender
の定義を確認してみましょう。
const { wrappedRowRender, tooltipContent, tooltipTrigger } =
useRender(props)
useRender
からwrappedRowRender
の定義を抜粋したものです。
renderExpanded
での結果を返しています。
では、renderExpanded
の定義を確認してみましょう。
const wrappedRowRender = (row: T, $index: number) => {
const store = props.store
const { isRowExpanded, assertRowKey } = store
const { treeData, lazyTreeNodeMap, childrenColumnName, rowKey } =
store.states
const columns = store.states.columns.value
const hasExpandColumn = columns.some(({ type }) => type === 'expand')
if (hasExpandColumn) {
const expanded = isRowExpanded(row)
const tr = rowRender(row, $index, undefined, expanded)
const renderExpanded = parent.renderExpanded
if (expanded) {
if (!renderExpanded) {
console.error('[Element Error]renderExpanded is required.')
return tr
}
// 使用二维数组,避免修改 $index
// Use a matrix to avoid modifying $index
return [
[
tr,
h(
'tr',
{
key: `expanded-row__${tr.key as string}`,
},
[
h(
'td',
{
colspan: columns.length,
class: `${ns.e('cell')} ${ns.e('expanded-cell')}`,
},
[renderExpanded({ row, $index, store, expanded })]
),
]
),
],
]
}
3. table-column
slots.default
がある場合にslots.default(data)
を返しています。
if (column.type === 'expand') {
// 对于展开行,renderCell 不允许配置的。在上一步中已经设置过,这里需要简单封装一下。
column.renderCell = (data) =>
h(
'div',
{
class: 'cell',
},
[originRenderCell(data)]
)
owner.value.renderExpanded = (data) => {
return slots.default ? slots.default(data) : slots.default
}
} else {
owner.value.renderExpanded = (data)
のdata
についてconsole.log(data.row)
を使ってデータを確認してみると、このようになりました。
あとは、slots.default(data)
がこのデータをtemplateタグ内のprops.row.age
とかに紐づけているわけですね。
{
"id": 1,
"name": "佐藤",
"age": 20,
"gender": "男"
}
【参考サイト】