跳转到内容

页码列表组件封装思路

刀刀
0字
0分钟
2026/8/16

前言

项目中封装业务组件两大原则:

  1. 不结合具体业务逻辑

    • 组件只作为数据容器,数据统一由父组件传入。
    • 只编写 UI 逻辑,具体数据操作这类业务逻辑,触发父组件监听交由父组件处理。
  2. 尽量提供简便

    在不结合具体业务逻辑的前提下,让父组件使用组件尽可能方便,原则是观察大部分页面的设计,能方便满足大多数页面需求。少数页面有差距,也有扩展方案。

组件搭建

Element Plus 组件库中的表格和分页器组件为基础,进行初步搭建。

table.vue
vue
<script setup>
import { ref } from 'vue'

const { tableData, total } = defineProps({
  tableData: {
    type: Array,
  },
  total: {
    type: Number,
  },
})
const emit = defineEmits(['changePage', 'changeSize'])

// 页码参数对象
const pageParams = ref({
  page: 1,
  pageSize: 10,
})

// 页码切换让父组件去做处理
const changePage = (e) => {
  pageParams.value.page = val
  emit('changePage', pageParams.value)
}
// 单页码数据数量切换让父组件去做处理
const changeSize = (e) => {
  pageParams.value.pageSize = val
  emit('changeSize', pageParams.value)
}
</script>

<template>
  <el-table>
    <!-- 子组件无法控制渲染什么内容,留出<word text="插槽" />让父组件传入 -->
    <slot>
      <el-table-column
        v-for="(item, index) in tableColumn"
        :key="index"
        :prop="item.prop"
        :label="item.label"
      ></el-table-column>
    </slot>
  </el-table>

  <el-pagination
    :total="total"
    :layout="(prev, pager, next, sizes)"
    @current-change="changePage"
    @size-change="changeSize"
  />
</template>

目前已尽可能简便地搭建组件,但父组件用起来仍很麻烦,与不封装直接用没有太大区别。

因此需要观察大部分页面和接口,加入部分业务逻辑,使其满足大部分页面。同时也要让极端情况可扩展自定义。

翻页逻辑

子组件修改页码组件让父组件更方便:

  1. 单页码数据数量切换给定默认值,同时支持父组件扩展自定义传入
  2. 后端接口一般保持统一性,页码单词不会突然改变。因此父组件无需监听两个事件,一个事件即可
table.vue
vue
<script setup>
import { ref } from 'vue'

const { tableData, total, pageSizes } = defineProps({
  tableData: {
    type: Array,
  },
  total: {
    type: Number,
  },
  pageSizes: {
    type: Array, 
    default: () => [10, 20, 30], 
  }, 
})
const emit = defineEmits(['changePage', 'changeSize']) 
const emit = defineEmits(['getPage']) 

// 页码参数对象
const pageParams = ref({
  page: 1,
  pageSize: 10,
})

// 页码切换让父组件去做处理
const changePage = (e) => {
  pageParams.value.page = val 
  emit('changePage', pageParams.value) 
} 
// 单页码数据数量切换让父组件去做处理
const changeSize = (e) => {
  pageParams.value.pageSize = val 
  emit('changeSize', pageParams.value) 
} 

// 分页器触发切换让父组件去做处理
const changePage = (e) => {
  pageParams.value.page = val 
  emit('getPage', pageParams.value) 
} 
</script>

<template>
  <el-table>
    <!-- 子组件无法控制渲染什么内容,流出插槽让父组件传入 -->
    <slot></slot>
  </el-table>

  <el-pagination
    :total="total"
    :layout="(prev, pager, next, sizes)"
    :page-size="pageSize[0]"
    :page-sizes="pageSizes"
    @current-change="getPage"
    @size-change="getPage"
  />
</template>

父组件传入对应的参数:

vue
<script setup>
import { onMounted } from 'vue'
import PageList from './components/pageList.vue'
import axios from 'axios'

const initList = async (e) => {
  axios
    .get('http://xxxxx', {
      params: { ...e },
    })
    .then((res) => {
      console.log(res)
    })
}
const getPage = (e) => {
  initList(e)
}

onMounted(() =>
  initList({
    page: 1,
    pageSize: 10,
  }),
)
</script>

<template>
  <page-list
    :tableData="tableData"
    :total="400"
    :pageSizes="[50, 100, 150]"
    @getPage="getPage"
  />
</template>

表格子行

父组件要使用这个封装的组件时,需要通过插槽设置表格行,代码如下所示:

vue
<template>
  <page-list :tableData="tableData" @getPage="getPage">
    <el-table-column prop="name" label="名称"></el-table-column>
    <el-table-column prop="price" label="价格"></el-table-column>
    <el-table-column label="操作">
      <el-button>删除</el-button>
      <el-button>编辑</el-button>
    </el-table-column>
  </page-list>
</template>

但此方式不够便捷,需要为插槽提供大量代码。

若需简化,先观察大部分页面表格的使用。大多数是表格渲染数组数据,必要时加上操作。

因此可不再传插槽,直接传递一个数组变量,内含多个对象,每个对象提供 propname 属性。子组件接收并遍历渲染即可。代码如下:

父组件:

vue
<script setup>
const tableColumn = [
  {
    prop: 'name',
    name: '名称',
  },
  {
    prop: 'price',
    name: '价格',
  },
]
</script>

<template>
  <page-list
    :tableData="tableData"
    @getPage="getPage"
    :tableColumn="tableColumn"
  />
</template>

子组件在默认插槽中获取并循环遍历。

注意

这里用默认插槽是因为还需考虑部分极端情况,要提供其他具名插槽给父组件使用

vue
<script setup>
import { ref } from 'vue'

const { tableColumn } = defineProps({
  // ...
  tableColumn: {
    type: Array,
    default: () => [10, 20, 30],
  },
})

// ...
</script>

<template>
  <el-table>
    <!-- 子组件无法控制渲染什么内容,流出插槽让父组件传入 -->
    <slot>
      <el-table-column
        v-for="(item, index) in tableColumn"
        :key="index"
        :prop="item.prop"
        :label="item.label"
      ></el-table-column>
      <!-- 假设每个页面都要有删除和编辑按钮(更精细点应该提供变量让父组件设置需不需要 -->
      <el-table-column label="操作">
        <el-button>删除</el-button>
        <el-button>编辑</el-button>
      </el-table-column>
    </slot>
  </el-table>

  <!-- ... -->
</template>

在极端情况下,父组件想要也使用自己的按钮,此时需要支持父组件传入按钮数组,子组件循环遍历。

父组件代码如下所示:

vue
<script setup>
const buttonList = [
  {
    emit: 'stop',
    name: '禁用',
    type: 'danger',
  },
]

// 子组件点击禁用按钮
const stopFn = (row) => {
  console.log(row)
}

// ...
</script>

<template>
  <page-list
    :buttonList="buttonList"
    :tableData="tableData"
    @getPage="getPage"
    :tableColumn="tableColumn"
    @stop="stopFn"
  />
</template>

子组件做如下操作:

  1. 获取数组循环遍历渲染
  2. 为按钮绑定点击事件,传入该行的数据以及按钮的自定义事件

代码如下:

vue
<script setup>
import { ref } from 'vue'

const { tableColumn, buttonList } = defineProps({
  // ...
  tableColumn: {
    type: Array,
    default: () => [10, 20, 30],
  },
  buttonList: {
    type: Array,
    default: () => [],
  },
})

// 点击操作模块的按钮
const handleClickFn = (emitName, row) => {
  emit(emitName, row)
}

// ...
</script>

<template>
  <el-table>
    <!-- 子组件无法控制渲染什么内容,流出插槽让父组件传入 -->
    <slot>
      <el-table-column
        v-for="(item, index) in tableColumn"
        :key="index"
        :prop="item.prop"
        :label="item.label"
      ></el-table-column>
      <!-- 假设每个页面都要有删除和编辑按钮(更精细点应该提供变量让父组件设置需不需要 -->
      <el-table-column label="操作">
        <template #default="scope">
          <el-button>删除</el-button>
          <el-button>编辑</el-button>
          <el-button
            v-for="item in buttonList"
            :key="item.emit"
            @click="handleClickFn(item.emit, scope)"
            >{{ item.name }}</el-button
          >
        </template>
      </el-table-column>
    </slot>
  </el-table>

  <!-- ... -->
</template>

总结

  1. 组件只作为数据容器,数据父组件传入
  2. 只做最简单的逻辑,深层次逻辑由父组件处理
  3. 尽可能让父组件使用简便,但也要考虑特殊情况

贡献者

The avatar of contributor named as duyidao duyidao
The avatar of contributor named as v_duyilin v_duyilin
The avatar of contributor named as 平安喜乐 平安喜乐
The avatar of contributor named as 刀刀 刀刀

页面历史

刀刀博客累计访客 人;文档累计访问量共