写在前面
经常开发后台管理系统的前端同学都有这样的感悟,后台系统里充斥着大量的表格、表单和图表;对这三个方向的内容组件化封装,就可以极大的提升开发效率,快速生成页面,快速交付。
今天我们主要来聊一聊表单
表单方案应该考虑什么
性能?校验?开发体验?
我们从实现一个登录表单出发
在刀耕火种时代,我们是这么编写的
const login = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = () => {
if(!username){
message.error('账号不能为空')
}
/* 校验 */
ajax({username, password})
}
return <div>
<div>
<span>账号:</span>
<Input value={username} onChange={e => setUsername(e.target.value)} />
</div>
<div>
<span>密码:</span>
<Input value={password} onChange={e => setPassword(e.target.value)} type="password" />
</div>
<button onClick={handleSubmit}>提交</button>
</div>
}
不管代码如何,确实能完美的解决我们的业务场景;
如果表单方案能接管输入校验和数据,那么真是帮了我们大忙。
查阅一些组件库,不难找到这样的代码
const login = () => {
const onFinish = (values) => {
console.log('Success:', values);
};
return <Form initialValues={{ remember: true }} onFinish={onFinish}>
<Form.Item
label="Username"
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="Password"
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Input.Password />
</Form.Item>
<Form.Item name="remember" valuePropName="checked" label={null}>
<Checkbox>Remember me</Checkbox>
</Form.Item>
<Form.Item label={null}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
}
初始值使用initialValues,数据的中间态由表单组件消费,校验也统统交给表单来负责,作为组件的使用方只关心表单点击提交后的数据,只需传入onFinish方法,便可以从方法的入参中得到该表单所有的数据。
是的,似乎是完美的方案了。
万恶的PM提出了新的要求,我希望验证码和密码是互斥的,就像大部分的登录页面一样,可以选择手机号+验证码的方式登录,也可以选择账号+密码的方式登录。我们需要把当前使用的方法类型也提交到后端去。
上述方案中我们不关心表单中数据的流向问题,但是我们似乎需要关心关心类型字段了;
当我们选择了账号登录,那么验证码的表单项就应该隐藏;反之密码框则隐藏;
查阅表单库的api文档似乎也能解决现在遇到的问题,但是代码就显得十分臃肿了。那么有没有比较完美的方案来解决遇到的问题呢。
动态表单
前端动态表单是一种在网页浏览器端(即前端)能够根据用户交互或者系统预设的规则动态改变其结构、内容和行为的表单。与传统的静态表单不同,静态表单的字段、布局等在页面加载时就已经固定,而动态表单可以在运行时进行添加、删除、隐藏 / 显示字段,改变字段类型、验证规则等操作。
以上文案复制来源为豆包。
看AI提供的动态表单解释,可以隐藏显示字段、校验规则,似乎比我们之前的方案更加高级了一些,也可以完美解决PM提供更高级别的要求。
我们拿formilyjs来实现类似的效果
const login = () => {
return <Tabs.TabPane key="1" tab="账密登录">
<Form
form={normalForm}
layout="vertical"
size="large"
onAutoSubmit={console.log}
>
<Field
name="username"
title="用户名"
required
decorator={[FormItem]}
component={[
Input,
{
prefix: <UserOutlined />,
},
]}
/>
<Field
name="password"
title="密码"
required
decorator={[FormItem]}
component={[
Password,
{
prefix: <LockOutlined />,
},
]}
/>
<Submit block size="large">
登录
</Submit>
</Form>
</Tabs.TabPane>
}
代码摘录为formilyjs.org,虽然与我们设想有些出入,但是查阅文档可以找到reactions的写法
reactions: async (field: Field) => {
if (field.query('type').value() === '账号登录') {
field.hidden = false;
} else {
field.hidden = true;
}
},
},
只需要简单书写,即可根据表单项来控制表单项的显示隐藏
至此,我们选用formilyjs作为系统的表单方案
我们希望书写上更为简洁一些,对 <Field ``/>组件做一些简单封装
return <>
<CustomFieldComponent
name={name}
title={title}
required={required}
dataSource={dataSource}
validator={validator}
reactions={reactions}
readPretty={readPretty}
hidden={hidden}
description={description}
{...fieldProps}
decorator={[FormItem, formItemProps]}
component={[Component, props]}
/>
</>
{/* {tips ? <div className="field-form-tips">{tips}</div> : null} */}
举个例子
我拿我们项目中修改密码的表单做一个例子
目前简单表单都可以使用配置化的方案来实现,工时几乎忽略不计。