一文学会vue3怎么自界说hook钩子函数和封装组件

一、前语

大家好,今日来聊聊vue3中怎么使自界说hook及怎么对组件进行封装,下面我将基于elememnt-plus组件库,封装一个支撑分页和过滤数据的表格组件,主要经过组合式api的办法,对table表格的一些公共逻辑组合成一个useTable钩子函数,再对相关组件进行灵敏封装,以适应更多需求的变化。

关键:vue3组合式api、ts、hook钩子函数、组件封装。

二、分页表格hook函数封装

下面经过一个未作任何封装的用户数据检查页面,进行分析,逐渐拆分组件和提取公共特点和办法。

1.未作封装时的页面

一文学会vue3怎么自界说hook钩子函数和封装组件

咱们能够从中提取一些每个页面都会重写一遍的特点或办法进行封装成useTable函数:

(1)数据列表:list

(2)总数:total

(3)当前页索引:pageIndex

(4)每页条目数量:pageSize

(5)是否正在获取数据:loading

(6)获取数据办法: getList

一个基础的分页表格用过必备以上内容,所以先将其提取到hook函数当中,其中获取数据办法是不确定的,所以需求运用useTable办法时,经过参数的办法传递进来进行初始化。除此之外list的数据类型还不能确定,所以可经过泛型的办法,在函数调用时进行传递。

2.useTable钩子函数实现

一文学会vue3怎么自界说hook钩子函数和封装组件

为了保持灵敏性,初始化时默许不考虑过滤参数,而是在获取数据办法参数进行传递,因为进入页面时一般都不会有过滤选项,如果有需求,可自行在初始化时进行扩展。

note: 你或许也留意到了获取数据时对filterOptions参数运用toValue()办法进行了解构,这个办法是vue3.3+新增的api,能够将值、refs 或 getters 规范化为值。这与unref()相似,不同的是此函数也会规范化 getter 函数。如果参数是一个 getter,它将会被调用而且回来它的回来值。

想了解更多关于信息可参考:toValue()官方文档

3.运用useTable前后对比

运用useTable时

一文学会vue3怎么自界说hook钩子函数和封装组件

未运用useTable时

一文学会vue3怎么自界说hook钩子函数和封装组件

经过以上对比能够明显看到运用useTable后的页面简练了许多,当需求再增加一个页面时,不必再重复界说多个特点,只需几行代码直接引进即可,直接减少了代码的冗余。

4.Table表格组件封装

一文学会vue3怎么自界说hook钩子函数和封装组件
二次封装为了款式统一性及更好的保护。

5.Pagination分页组件封装

一文学会vue3怎么自界说hook钩子函数和封装组件
二次封装为了款式统一性及更好的保护。

三、数据过滤表单封装

数据展示则少不了数据过滤这一环节,所以能够封装一个过滤表单,鉴于需求对过滤表单进行重置,所以可将表单的ref实例放到组件内部,这样也不必每个页面都重写表单重置办法了。组件外部只处理又过滤表单触发的事件即可。

FilterForm表单组件封装

一文学会vue3怎么自界说hook钩子函数和封装组件

FilterForm表单运用

一文学会vue3怎么自界说hook钩子函数和封装组件
一文学会vue3怎么自界说hook钩子函数和封装组件

由于query表单信息每个页面是不固定的,所以在页面中保护即可。

四、布局组件封装

到这里咱们已经有了过滤表单组件、表格组件、分页组件,当运用时咱们不或许直接运用就完事了,知识还得对其边距进行调整。咱们都知道运用组件库时都会有布局组件,咱们也能够参考其封装布局组件来对款式及加载状况进行统一管理。

TableLayout布局组件封装

一文学会vue3怎么自界说hook钩子函数和封装组件

五、页面完好运用示例

一文学会vue3怎么自界说hook钩子函数和封装组件

六、结语

当运用vue3进行开发时应该打破vue2时运用的选项式API思想,只有破冰后才能更好的拥抱hook,拥抱前端的未来。最后也引荐一个vue官网引荐学习的VueUse组合式函数调集,帮助你提前拥抱hook。

源码

useTable.ts

import { ref, toValue, watchEffect } from 'vue'
// 接口回来数据结构
interface IResResult<IData> {
  data: IData,
  total: number
}
// 初始化参数类型
type TProps<IData> = {
  // 获取数据办法
  fetchData: (...args: any) => Promise<IResResult<IData[]>>
}
export const useTable = <IData>({
  fetchData
}: TProps<IData>) => {
  // 数据列表
  const list = ref<IData[]>()
  // 总数
  const total = ref(0)
  // 当前页
  const pageIndex = ref(1)
  // 每页显现条目个数
  const pageSize = ref(2)
  // 加载状况
  const loading = ref(false)
  // 获取数据办法
  const getList = (filterOptions?: object): Promise<IResResult<IData[]>> => {
    loading.value = true
    return new Promise((resolve, reject) => {
      fetchData({
        index: pageIndex.value,
        size: pageSize.value,
        // 将值、refs 或 getters 规范化为值
        ...toValue(filterOptions)
      }).then(res => {
        list.value = res.data
        total.value = res.total
        // 如果需求在组件中特殊处理,回来数据
        resolve(res)
      }).catch(err => {
        console.log('获取数据失败!', err)
        reject(err)
      }).finally(() => {
        loading.value = false
        console.log('接口调用完成!')
      })
    })
  }
  // 页大小改变
  const onSizeChange = (size: number) => {
    pageSize.value = size
  }
  // 翻页
  const onIndexChange = (index: number) => {
    pageIndex.value = index
  }
  watchEffect(() => {
    // 运用watchEffect可监听getList办法中运用的响应式依赖变化后履行办法
    getList()
  })
  return {
    list,
    total,
    pageIndex,
    pageSize,
    loading,
    onSizeChange,
    onIndexChange,
    getList
  }
}

TableLayout.vue

<!-- 表格布局组件,用来操控布局款式 -->
<template>
  <div class="table-layout" v-loading="loading">
    <div class="header">
      <slot name="header"></slot>
    </div>
    <div class="body">
      <slot name="body"></slot>
    </div>
    <div class="footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>
<script setup lang="ts">
defineProps<{
  loading: boolean
}>()
</script>
<style scoped>
.table-layout {
  background-color: #fff;
  padding: 10px;
}
.footer {
  margin-top: 20px;
}
</style>

FilterForm.vue

<template>
  <div class="filter">
    <div class="form">
      <el-form ref="form" inline v-bind="$attrs">
        <slot></slot>
      </el-form>
    </div>
    <div class="hanle">
      <el-button type="primary" @click="submit">查找</el-button>
      <el-button @click="reset">重置</el-button>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { FormInstance } from 'element-plus'
const emit = defineEmits<{
  (e: 'submit'): void,
  (e: 'reset'): void
}>()
const form = ref<FormInstance>()
const submit = () => {
  emit('submit')
}
const reset = () => {
  form.value?.resetFields()
  emit('reset')
}
</script>
<style scoped lang="scss">
.filter {
  display: flex;
  .hanle {
    margin-left: 20px;
  }
}
</style>

Table.vue

<template>
  <el-table :data="data">
    <slot></slot>
  </el-table>
</template>
<script setup lang="ts">
defineProps<{
  data?: object[]
}>()
</script>
<style scoped>
</style>

Pagination.vue

<template>
  <el-pagination
    :page-sizes="[1, 2, 3, 4]"
    layout="total, sizes, prev, pager, next, jumper"
  />
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>