V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
monkindey
V2EX  ›  前端开发

ahooks • useTable - 查询表格场景插件化 Hook 解决方案

  •  
  •   monkindey ·
    monkindey · 2020-08-26 10:00:23 +08:00 · 1269 次点击
    这是一个创建于 1339 天前的主题,其中的信息可能已经有所发展或是发生改变。

    为什么要有 useTable ?


    在中后台场景中,查询表格的场景占比较高。你会发现该场景写起代码会充斥着重复性的代码,比如点击下一页的时候,你需要把表单查询条件带过去,点击查询的时候你需要把页码归为 1 等常规逻辑。当然还有很多增强功能,比如排序、filter 、多选功能。

    我们面临“高效”和“灵活”权衡问题,高效的话可以一个组件来实现上面功能然后通过 props 来配置,这样子可能会减少了灵活性,灵活又可能需要使用方手写很多代码才能完成一个功能,失去了高效性。

    最后借鉴了 Egg 和 Babel 的思想,通过 Core + Plugin 的方式来平衡“高效”和“灵活”。同时设计的过程中,我们减少新概念出现,尽量使用业界通用的方案来减少学习成本。我们花费大量时间去思考插件设计,并且数字供应链已经做第一个吃螃蟹的人,基本上所有的查询表格场景都用这个方案了。现在是时间放出来让大家 Review 下了,期望有更多场景的输入,这样子 useTable 才能做得更好。

    useTable 是什么?


    useTable 针对的是查询表格场景的一些状态逻辑处理,比如请求的时候有 loading,请求成功之后设置 dataSource,点击下一页的时候请求等。本身不关心 View 层,并不是重新造一个 Table 组件。

    它包含以下特性:

    • 🚀 可扩展:可定制能力强,插件易开发,方便与 Fusion 、Antd 等 Design 系统集成
    • 📦 开箱即用:提供多个场景的插件,方便快速支持不同场景的需求
    • 💡 Hooks:全部基于 Hook 实现,拥抱 React 新特性,减少学习成本


    整体概览:

    image.png

    最底层 useTable 具备插件能力,可以通过插件生成具备插件能力的 Hook,可以一直套娃下去,然后适配不同 Design 系统。上层建设可以多种多样,底层可以共用一套插件技术体系。

    基于前面所遇到的场景和思考,我们发布了 ahooksjs useTable v0.1.0 版本让大家可以尝鲜。

    useTable 具备了什么能力?


    useTable 提供核心 useTable & useFormTable 规范,还有对应 next 版本的 useNextTable & useNextFormTable,方便使用 Fusion Next 的开发者快速使用,后续也会支持 Antd,还提供官方多个场景插件。下面一一介绍下:

    • @ahooksjs/use-table:核心底层具备插件能力的 useTable 规范
    • @ahooksjs/use-form-table:核心底层具备插件能力的 useFormTable 规范
    • @ahooksjs/next-table:底层 useTable 的 next 版本
    • @ahooksjs/next-form-table:底层 useFormTable 的 next 版本
    • @ahooksjs/use-selection-plugin:多选插件
    • @ahooksjs/use-sortable-plugin:排序插件
    • @ahooksjs/use-filter-plugin:过滤插件
    • @ahooksjs/use-tree-plugin:懒加载树插件
    • @ahooksjs/use-async-default-plugin:异步默认值插件

    那怎么使用呢?


    上面说了这么多,是时候看看真面目了,是骡子是马拉出来遛遛。下面主要是一些伪代码,主要为了简单描述下如何使用,更多可以看官网的快速开始

    简单版


    useNextFormTable 会返回不同组件的 Props,然后可以赋给对应的组件,useNextFormTable 帮你管理状态逻辑,现在看起来并没有什么特别之处。

    const Component = () => {
      const { formProps, tableProps, paginationProps } = useNextFormTable(list);
      return (
        <div>
          <SchemaForm {...formProps} components={{ Input }} style={{ marginBottom: 20 }} inline>
            <Field name="name" title="name" x-component={'Input'} />
            <FormButtonGroup>
              <Submit>查询</Submit>
              <Reset>重置</Reset>
            </FormButtonGroup>
          </SchemaForm>
          <Table {...tableProps}>
            <Table.Column title="name" dataIndex="name" width={200} />
          </Table>
          <Pagination style={{ marginTop: 16 }} {...paginationProps} />
        </div>
      );
    };
    


    上面只是完成一个最 Basic 的能力,那如果需要加上其他功能比如多选,我们如何实现呢?我们提供的方案是 useNextFormTable 具备扩展能力,然后根据场景沉淀出不同插件,并且这些插件可以同时使用。具体可以看下下面的例子是如何使用的?

    插件版


    加上多选的功能,多选的插件已经帮你完成下面的功能了

    • 设置 Table props,比如 rowSelection ;
    • 监听事件,比如 onSelect ;
    • 重新查询之后要清除选中项;
    import "@alifd/next/dist/next.css";
    import React from "react";
    import useNextFormTable from "@ahooksjs/next-form-table";
    import useSelectionPlugin from "@ahooksjs/use-selection-plugin"
    import { Table, Pagination } from "@alifd/next";
    import { SchemaForm, Field, Submit, Reset, FormButtonGroup } from "@formily/next";
    import { Input } from "@formily/next-components";
    
    export default function App() {
      const plugin = useSelectionPlugin()
      const { formProps, tableProps, paginationProps } = useNextFormTable(list, {
        plugins: [plugin]
      });
    
      return (
        <div style={{ padding: 20 }}>
          <SchemaForm
            {...formProps}
            components={{ Input }}
            style={{ marginBottom: 20 }}
            inline
          >
            <Field name="name" title="name" x-component={"Input"} />
            <FormButtonGroup>
              <Submit>查询</Submit>
              <Reset>重置</Reset>
            </FormButtonGroup>
          </SchemaForm>
    
          <Table {...tableProps}>
            <Table.Column title="name" dataIndex="name" width={200} />
          </Table>
          <Pagination style={{ marginTop: 16 }} {...paginationProps} />
        </div>
      );
    }
    
    


    基本上你只需要引入多选插件,然后直接使用它就可以完成多选功能,而不需要在各个地方写逻辑。当然多插件一起使用也是支持的,文档上有对应的例子

    如何定制?


    如果官方插件不能满足你的要求的,你完全可以自定义自己的插件,还有如果你部门也有自己的 Design 系统,组件的 props 定义不一样,可以看文档的『插件开发』和『结合 Design』,帮助你快速定制符合你具备插件化能力的查询表格。

    上层建设


    上层建设可以多种多样,你可以用源码开发,也可以通过数据驱动,还可以通过配置化的方式,底层共用同一套插件体系。useTable 不关心上层建设,只是提供方便上层建设的能力。

    需要你


    ahooks useTable 现在还是很年轻,需要更多场景的输入才能越做越好。大家们有任何问题或者建议都可以提到 Github Issue 上,我们会第一时间处理。

    5 条回复    2020-08-26 11:45:23 +08:00
    otakustay
        1
    otakustay  
       2020-08-26 10:16:00 +08:00   ❤️ 2
    我认为在 hook 上这么玩 plugin 的方案非常扯谈,hook 本来就是一套函数式的设计,基于原子的数据来增强就好
    const tableProps = useTable(list);
    const propsWithSelection = useSelection(tableProps);
    const propsWithSort = useSort(propsWithSelection);

    这样甚至可以很快速地构建出适合自己的 hook:
    const useOwnTable = compose(useTable, useSelection, useSort);

    任何一个东西,加上插件就是在增加概念,插件不是一个公认的逻辑,每一个插件系统的实现和设计都是不一样的,让人去理解成本并不低
    monkindey
        2
    monkindey  
    OP
       2020-08-26 10:37:35 +08:00
    @otakustay plugin 其实就是一个 hook,只是一个高级 hook,返回对应的属性而已。而且多选、排序都是依赖查询流程的,如果你只是关心 props 的话,没办法一下就完成一个功能,比如多选勾选的时候,重新点击查询会清空掉勾选的值,排序也是一样的情况。

    其实 useTable 是具备插件合并的能力,每一个插件也是一个 hook,你可以理解 useTable 就是一个 compose,useTable 、useSelction 、useSort 是一个插件。也就是你的理解:

    const useOwnTable = compose(useTable, useSelection, useSort);

    相当于

    const useOwnTable = useTable(useSelection, useSort)
    monkindey
        3
    monkindey  
    OP
       2020-08-26 10:44:48 +08:00
    @otakustay 插件你可以理解是增加一个概念,但是里面用的东西都不是新概念,都是一些业界流行的概念合并在一起。因为我们要写一个地方就可以解决一个功能的,不需要我加一个多选的功能或者排序的功能都要用 useState 状态管理,然后设置 props,然后在请求的链路中还要去清除勾选项,你会发现代码写起来就很繁琐,特别是当你负责很多类似页面的时候。
    otakustay
        4
    otakustay  
       2020-08-26 11:16:23 +08:00
    > 而且多选、排序都是依赖查询流程的,如果你只是关心 props 的话,没办法一下就完成一个功能

    我会选择先弄一个 useTableParams 的东西来组装参数,useTableXxxParams 追加参数

    算是设计理念上的不同吧,我比较喜欢“如无必要,勿增概念”的玩法
    monkindey
        5
    monkindey  
    OP
       2020-08-26 11:45:23 +08:00
    @otakustay 额额,同意你的设计理念,我也喜欢简单。我也思考很久,说服很多人,整一个设计并没有加概念,插件其实一个 hook,里面有 middleware 还有 compose 都是业界通用方案,只是在这里把它们合在一起。

    组装参数只是一部分,还有在查询前做事情,在查询后做事情,比如我们这边有人实现一个服务端驱动的插件,还可以拦截 response 做格式化。

    因为我不知道你们有没有做过中台页面,可能代入感比较弱。你提的问题我们这边在对这个方案的时候也提出来过,但是最后都认同了。如果你想进一步交流的话,可以加我微信 atob('bW9ua2luZGV5') ( console 执行下 )
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5670 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 02:30 · PVG 10:30 · LAX 19:30 · JFK 22:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.