Vue 3에서 동적 슬롯 이름으로 테이블 구성 요소 빌드

Vue 3(및 2)의 가장 멋진 부분 중 하나는 종종 완전히 간과되는 기능입니다. 동적 이름과 함께 slot을 사용할 수 있는 기능입니다.

무엇보다도 이것은 자식 배열 내에서 원하는 위치에 정확하게 데이터를 주입하는 정말 강력한 방법을 제공합니다.

그것은 우리에게 무엇을 의미합니까? 자, 알아보기 위해 SimpleTable 구성 요소를 빌드해 보겠습니다.
items 이라는 개체 배열을 기반으로 행을 자동으로 빌드하는 테이블 구성 요소를 제공한다고 가정해 보겠습니다.

const items = ref([
  { id: 1, title: 'First entry', description: 'Lorem ipsum' },
  { id: 1, title: 'Second entry', description: 'Sit dolar' },
])


열을 빌드하기 위해 fields이라는 다른 개체 배열을 사용하겠습니다.

const fields = ref([
  { key: 'title', label: 'Title' },
  { key: 'description', label: 'Description' }
])

itemsfields이 정의된 상태에서 테이블을 다음과 같이 사용하고 싶습니다.

<SimpleTable :items="items" :fields="fields" />


항목과 필드를 반복하는 몇 개의 v-for 문과 약간의 논리만 있으면 행과 열을 자동으로 생성하는 SimpleTable 구성 요소를 구축할 수 있습니다.

<template>
  <table>
    <thead>
      <tr>
        <th v-for="field in fields" :key="field.key">
          {{ field.label }}
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="item in items" :key="item.id">
        <td v-for="key in displayedFieldKeys">
          {{ item[key] }}
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script lang="ts" setup>
import { computed, PropType } from 'vue';

interface TableField {
  key: string
  label: string
}

interface TableItem {
  id: number
  [key: string]: unknown
}

const props = defineProps({
  fields: {
    type: Array as PropType<TableField[]>,
    default: () => []
  },
  items: {
    type: Array as PropType<TableItem[]>,
    default: () => []
  }
})

const displayedFieldKeys = computed(() => {
  return Object.entries(props.fields).map(([_key, value]) => value.key)
})
</script>


완전 멋지죠!? 그러나 필드 키를 기반으로 멋진 테이블 셀의 내용을 수정하려면 어떻게 해야 할까요? 예를 들어 제목을 굵게 표시하거나 각 셀에 추가 데이터를 삽입합니다.

구조에 대한 동적 슬롯 이름!

다음 슬롯 중 하나에 각 테이블 셀의 내용을 래핑해 보겠습니다.

...
<tr v-for="item in items" :key="item.id">
  <td v-for="key in displayedFieldKeys">
    <slot
      :name="`cell(${key})`"
      :value="item[key]"
      :item="item"
    >
      {{ item[key] }}
    </slot>
  </td>
</tr>
...


이제 필드 키를 기반으로 셀 집합의 내용을 수정하려고 할 때마다 다음과 같이 할 수 있습니다.

<SimpleTable :items="items" :fields="fields">
  <template #cell(title)="{ value, item }">
    <p>A bold item title: <strong>{{ value }}</strong></p>
    <p>Item ID for some reason: {{ item.id }}</p>
  </template>
</SimpleTable>


니토! 이제 과도한 마크업을 처리할 필요 없이 수정하려는 콘텐츠를 정확히 찾아낼 수 있습니다.

도대체 나는 caption 지원, col 스타일 지정, 필드 숨기기 및 형식 지정, 셀에 th 또는 td 사용 여부 결정과 같은 몇 가지 추가 기능이 있는 이 테이블 구성 요소의 약간 더 강력한 버전을 구축했습니다.

열 정렬은 이 문서의 향후 개정판에서 제공될 예정입니다.

<template>
  <table>
    <caption v-if="!!$slots.caption || caption">
      <slot name="caption">{{ caption }}</slot>
    </caption>
    <colgroup>
      <template v-for="field in displayedFields" :key="field.key">
        <slot :name="`col(${field.key})`">
          <col>
        </slot>
      </template>
    </colgroup>
    <thead>
      <tr>
        <th v-for="field in displayedFields">
          <slot :name="`head(${field.key})`" :field="field">
            {{ field.label }}
          </slot>
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="item in items" :key="item.id">
        <template v-for="key in displayedFieldKeys">
          <Component :is="cellElement(key as string)">
            <slot
              :name="`cell(${key})`"
              :value="format(item, (key as string))"
              :item="item"
              :format="(k: string) => format(item, k)"
            >
              {{ format(item, (key as string)) }}
            </slot>
          </Component>
        </template>
      </tr>
    </tbody>
  </table>
</template>

<script lang="ts" setup>
import { computed, PropType } from 'vue';

interface TableField {
  key: string
  label: string
  format?: Function
  hidden?: boolean
  header?: boolean
}

interface TableItem {
  id: number
  [key: string]: unknown
}

const props = defineProps({
  fields: { type: Array as PropType<TableField[]>, default: () => [] },
  items: { type: Array as PropType<TableItem[]>, default: () => [] },
  caption: { type: String, default: null }
})

const displayedFields = computed(() => props.fields.filter((i) => !i.hidden))

const displayedFieldKeys = computed(() => {
  return Object.entries(displayedFields.value).map(([_key, value]) => value.key)
})

const cellElement = (key: string) => {
  const field = props.fields.find((f) => f.key === key)
  return field && field.header ? 'th' : 'td'
}

const format = (item: TableItem, key: string) => {
  const field = props.fields.find((f) => f.key === key)
  return field && field.format ? field.format(item[key]) : item[key]
}
</script>


도움이 되었기를 바랍니다.

감사!

좋은 웹페이지 즐겨찾기