LoginSignup
1
0

【Vue.js】Element Plusのtableにおけるスロットの動きをソース読んで超ざっくり理解してみた

Posted at

【概要】

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-tableel-table-columnというタグはvueのコンポーネントです。
とりあえず一番の親であるel-tableのコードを見てみます。
<slot />があるので、ここにel-table-column配下の要素が入りそうです。
しかし、レンダリングされた画面ではこの<slot />で宣言された箇所には要素が入っていません。
では、どこにレンダリングされるのでしょうか?

packages\components\table\src\table.vue
      <div ref="hiddenColumns" class="hidden-columns">
        <slot />
      </div>

<slot />で宣言された箇所には中身のないdivが3つあるだけです。

image.png

年齢・性別の拡張表示要素はtbody配下にレンダリングされているのが確認できます。

image.png

tbodyを表示しているのは下記箇所です。

packages\components\table\src\table.vue
            <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の定義を確認してみましょう。

packages\components\table\src\table-body\index.ts
  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の定義を確認してみましょう。

packages\components\table\src\table-body\index.ts
    const { wrappedRowRender, tooltipContent, tooltipTrigger } =
      useRender(props)

useRenderからwrappedRowRenderの定義を抜粋したものです。
renderExpandedでの結果を返しています。
では、renderExpandedの定義を確認してみましょう。

packages\components\table\src\table-body\render-helper.ts
  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)を返しています。

packages\components\table\src\table-column\render-helper.ts
    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": "男"
}

【参考サイト】

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0