LavaC

LavaC

Still finding my answer
github

一个月的Formily使用经验总结

背景#

因为工作原因近一个月换了完全没用过的技术栈开发新项目。React、Storybook 这些倒还好,基本一天就上手了,倒是 Formily 这厮陆续摸索了半个多月才算入门,趁一阶段工作结束空隙我就把这些经验总结一下。

因为项目原因我使用 Formily 的方式不一定是最佳实践甚至不一定是对的,对表单方面的使用也约等于零,主要场景集中于使用@formily/antd-v5和自制组件上。

基础#

Formily 是阿里开发的一套以 Schema 描述表单的开源框架,不过我们项目中使用它的理由并不是构建表单,而是用于动态构建页面。

Formily 里有三个比较重要的概念:Form、Field 和 Schema。

  • Form 是整个表单的核心,其实例可用于获取和设置表单的校验、值、字段、字段联动等内容
  • Field 是 Form 的组成部件,也就是字段,比如表单里设置了一个叫 name 为keyword的输入框,那每次修改输入框的内容时,form.values.keyword就会跟着变动。
  • Schema 是描述 Field 的数据,可以简单的认为其与 ReactNode 等价,具体一个 Schema 能包含什么内容可以参考官方文档
    • 在 Schema 转组件的过程中 Formily 会默认代理组件的valueonChange,所以要开发 Formily 组件时要考虑到这一点。

从一个简单的页面开始#

先从一个简单的示例开始
Pasted image 20241003170934.png

Pasted image 20241003171026.png

上面的代码创建一个 ==“带边框的 div,里面装着一个输入框和另一个带了两个输入框的容器”== 的场景,从代码中我们可以得到几个结论:

  • Schema 的层级结构相当于 HTML 中的层级结构,其中的properties起到了类似children的作用。
  • createForm中可以定义表单的一些初始状态?
  • x-component代表当前位置需要渲染的元素,可以支持原生的标签可自定义的组件,其中的自定义组件需要在createSchemaField中进行注册。
  • x-component-props中定义了组件的 props,x-decorator定义了包装这个组件的组件。

简单操作一下表单,可以发现 Schema 和实际的表单值的映射关系。
Pasted image 20241003171319.png
image.png

如果要管理不同字段的值,schema 的type非常关键。

  • typevoid的时候,表单会忽略这一层的路径。
  • typeobject的时候,该字段会成为承接子字段的对象。
  • type为基本类型的时候,该字段代表具体的值。

当时我们本来打算封装一个组合了 Card + Tabs 功能的组件,但是在 Formily 的机制下,字段本身不能既表示自己的值(Tabs 的 activeKey),也成为包含子字段的对象,所以最后打消了这种做法。

函数处理#

如果坚持使用 JSON Schema,那往 Schema 里塞函数的做法就显得不那么合理了。在 Schema 中,Formily 会把{{}}的字符串处理成函数,所以要转换一下写法。

// ...
	input: {
		type: "string",
		"x-component": "Input",
		"x-component-props": {
			onClick: `{{ (event) => { console.log(event) } }}`
		}
	}
// ...

同样的,对需要传 ReactNode 的 props 也是如此处理,不过需要使用React.createElement

import { createElement } from "react";
import { Input } from '@formily/antd-v5'
import { SearchOutlined } from '@ant-design/icons';

// ...
const SchemaField = createSchemaField({
	components: {
		Input
	},
	scope: {
		createElement,
		SearchOutlined
	}
})
// ...
input: {
	type: "string",
	"x-component": "Input",
	"x-component-props": {
		suffix: `{{ createElement(SearchOutlined) }}`
	}
}
// ...

字段联动#

这部分内容官网文档说得就挺好的,就不赘述了

数据传递#

我们当时的页面有一个联动逻辑,当在顶部导航栏切换时间后,下面的各个图表都要同步更新数据,而我们的解决方式如下,不一定是最佳实践。
image.png

组件开发流程#

Formily 组件和常规的 React 组件差别还是挺大的,如果把@formily/antd-v5作为官方的推荐实践方式的话,那就不能以之前的思路来开发组件。

以 antd 的 Table 为例,无论是columns还是dataSource都是作为 props 的一部分传递给组件的,但在 Formily 中,数据应只由于默认配置的props.value来处理,所以开发时应该做好渲染数据与props.value的转换。对接已有组件时也可以用官方的mapProps方法做映射。

要较真的话数据放在哪都是可以实现功能的,但能简单的从form.values拿到所有值还是比把数据分散到 field 的componentProps要方便的。

除此之外,善用useFormuseFielduseFieldSchema可以方便获取父子字段的内容。

一些坑#

ReactNode 转 Schema#

虽然可以用{{createElement}}的方式传递 ReactNode,但对于自定义组件来说还需要提前把组件传入 scope 中,对于动态的 Schema 来说,这难以做到按需导入。

我当时的做法是组件内部做拦截。用useFieldSchema拿到 Schema,然后判断对应的属性是否也是一个 Schema 对象,如果是则返回一个RecursionField来渲染 Schema。

拦截写完后乍一看还没什么毛病,但是在 antd 的 Table sortIcon 时就傻眼了,渲染的图标在点击后完全没有状态变化。

个人猜测这跟 Formily 的渲染机制有关,RecursionField的渲染结果被缓存了,只是重新运行函数传递新的 props 还不足以触发其重新渲染。

onChange#

还是跟 Table 的过滤有关,原流程中点击表头的排序会触发表格的onChange事件获得排序信息。

但我在尝试换了多种写法后发现仍不能触发这个onChange,看了源码后才发现为了防止冒泡官方给onChange覆盖了个空函数。你们是完全用不到这个功能吗?

业务层面没什么解决方法,这里最后用了 patch 的方式解决的,颇为麻烦。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。