或许这就是下一代组件库

13,452 阅读5分钟

自 react、vue 等数据驱动的框架流行以来,诞生了许多相关组件库,但这些组件库的编程模式基本都是大同小异,虽然这些年都在不断迭代,但除了功能更加丰满,长相更加好看之外,也基本上没有什么质的变化。在 material、antd 等垄断地位下,也很少有个人或公司去选择做组件库了,组件库也逐渐退出了各大会议论坛。但随着 react hooks、vue composition api 的推出,我想,或许组件库有了新的突破点。

从某种意义上来说,组件可以分为有状态组件和无状态组件(函数式组件),理想状态下,我们期望所有的组件都是无状态组件,但实际上,组件库提供的组件 90% 以上都是有状态组件,因为组件内部不得不要维护一套逻辑非常复杂的状态,这不仅让代码的易读性、可维护性降低,也增加了个性定制化组件的难度。antd 为了让用户能够自己定制化组件,底层提供了 rc-xxx 一系列更加原子化的组件,但依然会受限于 html 结构,导致一些场景无法满足。

如果说,我们能够将“状态“和 UI 彻底(理想状态)分开,组件都是无状态组件,只接收属性,负责展示,所有的状态逻辑交由一个独立的包(这里我们暂且叫他 use-hooks吧)去管理,会发生什么?

  • 我不喜欢 ant design 的设计,你可以只使用 use-hooks,去写一套自己的无状态组件库
  • 我害怕圣诞彩蛋,你可以只使用无状态组件库,去写一套自己的 use-hooks
  • react、vue 版本合并在一个包里不再是梦
  • ant design 将不仅是设计语言,还是组件库基础设施

当然,以上或许都是理想状态,但我们一直在往这个方向上努力。而且有了一定的成果,下面通过 Vue 的示例来进一步的体验。

Vue 3 最大的特性是 composition api,它提供了一种方式让我们可以更大限度的复用逻辑,也让我们在开发大型项目的时候,使得逻辑更加清晰,在我们开发 Ant Design Vue 2.0 的时候发现,除了逻辑复用,composition api 还能帮助我们做组件解耦。

在我们开发组件的时候一直尽可能的做到“高内聚、低耦合”,但是有些组件始终没能彻底解耦,尤其是 Form 表单组件,没错,流水的组件,铁打的 Form:

历史不再提,直接看现状: 我几乎查看了现有的基于 Vue 的所有主流组件库,包括 vuetify、element、vant 等,它们的 form 表单组件基本都是一个套路,当然也包括 ant-design-vue,都是在 form 中使用 provide 提供表单上下文,在 form-item 中使用 inject 注入表单上下文,input / select 等组件通过某种约定(或发布订阅、或依赖注入、或被动劫持)来和 form、form-item产生“通信“。这几个组件在源码层面就产生了各种耦合,对于使用层面,也不例外,各个组件之间必须按照“严格“的 API 规范进行传递参数,校验逻辑和展示强绑定。 还是举个🌰更直观,产品&设计要实现如下图示的一个表单,所有的错误信息统一在一处显示: image.png 尼玛,组件库不支持,要自研,实现成本高,要延期,和历史设计规范不一致,改设计,总之各种借口和理由开始各方撕逼了。

为了解决这一问题,当然不是撕逼的问题,而是组件逻辑和展示耦合的问题,我们提出了表单校验中心化处理的方案 —— useForm,表单的校验状态完全在useForm中去处理,对于a-form、a-form-item,仅仅用来做表单布局和样式,不再做校验的逻辑,他们之间也不用通过各种方法进行通信。甚至可以不需要 a-form,你可以使用任何html标签替代,当然为了语义化,多写几个字符串也没啥毛病。 上代码:

定义一个响应式的数据和表单域规则:

// 数据
const modelRef = reactive({
  name: '',
  region: undefined,
  type: [],
});
// 表单域规则
const rulesRef = reactive({
  name: [
    {
      required: true,
      message: 'Please input name',
    },
  ],
  region: [
    {
      required: true,
      message: 'Please select region',
    },
  ],
  type: [
    {
      required: true,
      message: 'Please select type',
      type: 'array',
    },
  ],
});

分别作为 useForm 的第一和第二参数,返回值中将包含响应式的校验结果信息以及其它的辅助方法:

const { resetFields, validate, validateInfos, mergeValidateInfo } = useForm(modelRef, rulesRef);

你可以将校验结果绑定到任意 a-form-item上,如:

<a-form-item v-bind="validateInfos.name"></a-form-item>

你不需要关心 validateInfos.name 里面都有什么值,当然如果你想做更加自定义化的UI,例如,你想将错误信息通过弹窗的形式展示:

if(validateInfos.name.validateStatus === 'error') {
  alert(validateInfos.name.message);
}

而 mergeValidateInfo 只是我们根据常用业务场景提供了一个合并校验信息的辅助方法,就像我们上方截图所示的合并错误信息在表单底部统一展示,代码如下:

const errorInfos = computed(() => {
  return mergeValidateInfo(...toArray(validateInfos));
});
<a-form-item class="error-infos" v-bind="errorInfos">
  <a-button type="primary" @click="onSubmit">
    Create
  </a-button>
</a-form-item>

完整代码可以通过 ant design vue 官网查看。 useForm 的 API 或许繁琐了些,但我更想分享的是一种思想,而且 useForm 的api 现在还处在讨论阶段,如果你有好的建议可以通过 issue 和我们互动交流。

除了 useForm,众多 useXxxx 都在讨论开发中,如果你有兴趣,可以发送简历到 antdv@foxmail.com,加入我们,一起打造下一代组件库,当然。同时项目组也有"海量"实习生名额。

注意:默认投递公司是蚂蚁金服战略投资的公司 —— 校宝在线,如果你要投递的是蚂蚁金服体验技术部,在邮件中特别注明。

最后的最后,useXxxx 是一个新的仓库,点个 star 再走呗 github.com/vueComponen…