[Vue.js] 关于 vue3 composable function(hook)的一些遐想


前提

最近初步完成了公司项目的 vue2 到 vue3 的迁移,目前正在用composable function也就是常说的 hooks 的开发范式来重构业务代码,与此同时我也开发了vue3-easy-data-table这款高可定制性的 data table 组件,在开始这篇文章之前,有必要简单的了解如何使用vue3-easy-data-table,关于这个组件可以参考我的另一篇介绍文章或者在线文档

定制 table footer

template refs + 计算属性:

为了让用户可以定制的 table footer ,vue3-easy-data-table暴露了一些分页相关的变量和方法:

截屏 2022-07-31 下午 11.44.57.png

我们可以通过template refs来获取和使用这些变量和方法,然后像下面的例子一样定制自己的 footer:

先上效果:

chrome-capture-2022-6-31.gif

代码:

<script lang="ts" setup>
import type { Header, Item } from "vue3-easy-data-table";
import { computed, ref } from 'vue';
import { mockClientItems } from "../mock"; // $ref dataTable
const dataTable = ref(); // index related
const currentPageFirstIndex = computed(() => dataTable.value?.currentPageFirstIndex);
const currentPageLastIndex = computed(() => dataTable.value?.currentPageLastIndex);
const clientItemsLength = computed(() => dataTable.value?.clientItemsLength); // pagination related
const maxPaginationNumber = computed(() => dataTable.value?.maxPaginationNumber);
const currentPaginationNumber = computed(() => dataTable.value?.currentPaginationNumber); const isFirstPage = computed(() => dataTable.value?.isFirstPage);
const isLastPage = computed(() => dataTable.value?.isLastPage); const nextPage = () => { dataTable.value.nextPage();
};
const prevPage = () => { dataTable.value.prevPage();
};
const updatePage = (paginationNumber: number) => { dataTable.value.updatePage(paginationNumber);
}; const headers: Header[] = [ { text: "Name", value: "name" }, { text: "Address", value: "address" }, { text: "Height", value: "height", sortable: true }, { text: "Weight", value: "weight", sortable: true }, { text: "Age", value: "age", sortable: true }, { text: "Favourite sport", value: "favouriteSport" }, { text: "Favourite fruits", value: "favouriteFruits" },
]; const items: Item[] = mockClientItems(200);
</script>

从上面的代码可以看出来,其实本质上就是通过 template refs 来获取vue3-easy-data-table内部暴露出来的变量,然后通过计算属性获得这些变量的 computed ref ,最后在 template 中使用这些 reactive 的变量并配合 css(或 scss)样式代码来定制 footer:

<template> <EasyDataTable ref="dataTable" :headers="headers" :items="items" :rows-per-page="10" :show-footer="false" /> <div class="customize-footer"> <div class="customize-index"> Now displaying: {{currentPageFirstIndex}} ~ {{currentPageLastIndex}} of {{clientItemsLength}} </div> <div class="customize-buttons"> <span v-for="paginationNumber in maxPaginationNumber" class="customize-button" :class="{'active': paginationNumber === currentPaginationNumber}" @click="updatePage(paginationNumber)" > {{paginationNumber}} </span> </div> <div class="customize-pagination"> <button class="prev-page" @click="prevPage" :disabled="isFirstPage">prev page</button> <button class="next-page" @click="nextPage" :disabled="isLastPage">next page</button> </div> </div>
</template> <style scoped>
.customize-footer { margin: 5px; display: flex; flex-direction: column; align-items: center;
}
.customize-footer div { margin: 5px;
}
.customize-button { display: inline-block; width: 20px; height: 20px; text-align: center; border-radius: 100%; cursor: pointer; padding: 3px; line-height: 20px;
}
.customize-button.active { color: #fff; background-color: #3db07f;
}
.customize-pagination button { margin: 0 5px; cursor: pointer;
}
</style>

Composable function ( hook )

如果我写一个函数,参数是 table 的 template ref ,返回值是分页相关的 state ,以及更新 state 用的 action ,那么这个函数不就是一个 composable function ( hook )了吗,于是我写了usePagination这个 hook:

usePagination.ts:

import { computed, Ref } from 'vue'; export type DataTableRef = Ref<null | { currentPageFirstIndex: number currentPageLastIndex: number clientItemsLength: number maxPaginationNumber: number currentPaginationNumber: number isFirstPage: boolean isLastPage: boolean nextPage: () => void prevPage: () => void updatePage: (page: number) => void
}> export function usePagination( dataTableRef: DataTableRef,
) { // index related const currentPageFirstIndex = computed(() => dataTableRef.value?.currentPageFirstIndex); const currentPageLastIndex = computed(() => dataTableRef.value?.currentPageLastIndex); const clientItemsLength = computed(() => dataTableRef.value?.clientItemsLength); // pagination related const maxPaginationNumber = computed(() => dataTableRef.value?.maxPaginationNumber); const currentPaginationNumber = computed(() => dataTableRef.value?.currentPaginationNumber); const isFirstPage = computed(() => dataTableRef.value?.isFirstPage); const isLastPage = computed(() => dataTableRef.value?.isLastPage); const nextPage = () => { dataTableRef.value?.nextPage(); }; const prevPage = () => { dataTableRef.value?.prevPage(); }; const updatePage = (paginationNumber: number) => { dataTableRef.value?.updatePage(paginationNumber); }; return { currentPageFirstIndex, currentPageLastIndex, clientItemsLength, maxPaginationNumber, currentPaginationNumber, isFirstPage, isLastPage, nextPage, prevPage, updatePage, }
} export type UsePaginationReturn = ReturnType<typeof usePagination>

使用:

<script lang="ts" setup>
import type { Header, Item } from "vue3-easy-data-table";
import { computed, ref } from "vue";
import { mockClientItems } from "../mock";
import { usePagination } from "use-vue3-easy-data-table";
import type { UsePaginationReturn } from "use-vue3-easy-data-table"; const dataTable = ref(); const { currentPageFirstIndex, currentPageLastIndex, clientItemsLength, maxPaginationNumber, currentPaginationNumber, isFirstPage, isLastPage, nextPage, prevPage, updatePage,
}: UsePaginationReturn = usePagination(dataTable); const headers: Header[] = [ { text: "Name", value: "name" }, { text: "Address", value: "address" }, { text: "Height", value: "height", sortable: true }, { text: "Weight", value: "weight", sortable: true }, { text: "Age", value: "age", sortable: true }, { text: "Favourite sport", value: "favouriteSport" }, { text: "Favourite fruits", value: "favouriteFruits" },
]; const items: Item[] = mockClientItems(200);
</script>

对比一下上面两种方法( template refs + 计算属性和 composable function ),是不是 composable function 的 script 部分更加简洁了,我把 computed 的定义都移到了usePagination这个 composable function 的内部,因为对于想要定制 footer 的用户来说,他们需要的结果就只是 footer 相关的 state ( reactive 的变量)和 action (方法),所以我就提供给他们相应的 composable function ( hook )用来返回他们想要的结果。

遐想

看到这里你有没有发现,其实 composable function ( hook )更好的将 view 层(html+css)和业务逻辑层(js)分开了,以后前端开发会不会有这样的一种分工模式呢,比如,团队有两个前端开发,一个前端开发 A 专注于 composable function ( hook )的开发,开发好之后将这些 composable function ( hook )提供给另一个前端开发 B ,前段开发 B 专注于 view 层( template+style )的开发。再或者说 view 基本交给设计师通过 figma 之类的设计软件来生成,前端开发从设计师那边拿到 html+css 后,通过 composable function ( hook )的开发来最终整合和开发前端程序呢?大家怎么觉得呢🤔?

最后

如果你觉得这篇文章不错,欢迎点赞,也欢迎给我个 github star⭐支持我,谢谢。
项目地址: https://github.com/HC200ok/vue3-easy-data-table

发表评论

您的电子邮箱地址不会被公开。