V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hc93
V2EX  ›  Vue.js

关于 vue3 composable function(hook)的一些遐想

  •  
  •   hc93 · 2022-07-31 23:45:37 +08:00 · 1658 次点击
    这是一个创建于 846 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前提

    最近初步完成了公司项目的 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

    1 条回复    2022-08-01 10:42:32 +08:00
    qiuyk
        1
    qiuyk  
       2022-08-01 10:42:32 +08:00
    这遐想有种让我回到了 10 年前的感觉,重新发明“重构”?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1000 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 23:08 · PVG 07:08 · LAX 15:08 · JFK 18:08
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.