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

2020-08-26 10:00:23 +08:00
 monkindey

为什么要有 useTable ?


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

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

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

useTable 是什么?


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

它包含以下特性:


整体概览:



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

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

useTable 具备了什么能力?


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

那怎么使用呢?


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

简单版


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 具备扩展能力,然后根据场景沉淀出不同插件,并且这些插件可以同时使用。具体可以看下下面的例子是如何使用的?

插件版


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

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 上,我们会第一时间处理。

1455 次点击
所在节点    前端开发
5 条回复
otakustay
2020-08-26 10:16:00 +08:00
我认为在 hook 上这么玩 plugin 的方案非常扯谈,hook 本来就是一套函数式的设计,基于原子的数据来增强就好
const tableProps = useTable(list);
const propsWithSelection = useSelection(tableProps);
const propsWithSort = useSort(propsWithSelection);

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

任何一个东西,加上插件就是在增加概念,插件不是一个公认的逻辑,每一个插件系统的实现和设计都是不一样的,让人去理解成本并不低
monkindey
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
2020-08-26 10:44:48 +08:00
@otakustay 插件你可以理解是增加一个概念,但是里面用的东西都不是新概念,都是一些业界流行的概念合并在一起。因为我们要写一个地方就可以解决一个功能的,不需要我加一个多选的功能或者排序的功能都要用 useState 状态管理,然后设置 props,然后在请求的链路中还要去清除勾选项,你会发现代码写起来就很繁琐,特别是当你负责很多类似页面的时候。
otakustay
2020-08-26 11:16:23 +08:00
> 而且多选、排序都是依赖查询流程的,如果你只是关心 props 的话,没办法一下就完成一个功能

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

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

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

因为我不知道你们有没有做过中台页面,可能代入感比较弱。你提的问题我们这边在对这个方案的时候也提出来过,但是最后都认同了。如果你想进一步交流的话,可以加我微信 atob('bW9ua2luZGV5') ( console 执行下 )

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/701473

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX