1.业务介绍
业务背景
商家有时无法在规定时间内发货,并且他们有正当理由:
- 1.封控导致快递停运。
- 2.自然灾害导致交通阻塞。
- 3.停电等突发情况。
- 4.重要会议(比如国家级别的)导致快递进不去
- 等等
在这些情况下,商家并非故意违约,因此平台不应该处罚商家。应提供给商家一个报备页面,提交报备之后,客服进行审核,如果审核通过,则免除对商家的惩罚。
业务需求(已经简化)
(为了叙述方便,简化了业务场景)商家在此表单需要填写:
1.报备原因。可选值有
- 会议赛事影响
- 自然灾害影响
- 停电、网络异常等等
2.受影响地区的类型。可选值有
- 发货地
- 收货地
3.受影响地区
- 如果受影响地区类型是发货地,那么可选择地区只有商家的仓库地址。
- 如果受影响地区类型是收货地,那么可选择地区是全国各地都可以选。
4.上传凭证
表单的联动规则是:
- 首次进入页面,仅仅展示
报备原因字段。隐藏受影响地区类型字段。展示受影响地区,但是状态是disable。隐藏上传凭证字段。 - 选择报备原因之后,展示
受影响地区类型字段。 受影响地区类型字段和报备原因字段都填写后,才可编辑受影响地区字段。- 改变
受影响地区类型,导致受影响地区的下拉列表改变。
- 如果
受影响地区类型是发货地,那么可选择的受影响地区只有商家曾经发货过的地区。 - 如果
受影响地区类型是收货地,那么可选择的受影响地区是全国地区。
报备原因、受影响地区类型、受影响地区全部选择后,才会展示上传凭证按钮。
2.为什么选择Formily
1.性能更好。
以上述简化版的业务为例。表单中有四个字段报备原因、受影响地区类型、受影响地区、上传凭证。
如果使用antd的Form组件,需要使用4个state分别记录4个字段的值。如果任意一个state发生改变,导致整个组件刷新,所以其他的字段也会刷新。
如果使用Formily,任意一个字段的值发生改变,只会刷新该字段对应的组件,而不会导致整个表单刷新。
2.业务组件更加纯粹,不需要管理Form表单的值
如果使用antd 的form,如何实现当报备原因为空,隐藏受影响地区类型?常规做法是使用一个state管理是否展示受影响地区类型
import React, { useState } from 'react';
import { Form, Input } from 'antd';
const Demo = () => {
const [showAreaType, setShowAreaType] = useState(false);
const handleChange = (e) => {
if (e.target.value) {
setShowAreaType(true);
} else {
setShowAreaType(false);
}
};
return (
<Form>
<Form.Item label="报备原因" name="reason">
<Input onChange={handleChange} />
</Form.Item>
{showAreaType && (
<Form.Item label="受影响地区类型" name="areaType">
<Input />
</Form.Item>
)}
</Form>
);
};
export default Demo;
如果使用Formily,则不需要在业务组件中记录Form表单的值。只需要在创建schema的时候,给受影响地区类型字段设置x-reactions。
const schema = {
type: "object",
properties: {
scene_type: {
required: true,
type: "string",
title: "报备原因",
"x-decorator": "FormItem",
"x-component": "SceneTypeSelector",
},
addr_type: {
type: "string",
title: "受影响地区类型",
"x-decorator": "FormItem",
"x-component": "InfluenceAreaType",
//x-reaction表示该字段依赖于其他字段。
//例如下面代码表示"受影响地区类型"依赖于"报备原因"
//当报备原因不是undefined的时候,"受影响地区类型"的display才是visible
"x-reactions": {
dependencies: ["scene_type"],
fulfill: {
state: {
display: `{{$deps[0] !== undefined ? 'visible' : 'none'}}`,
},
},
},
},
}
草稿(忽略)
1.1 性能较差
比如,一个字段改变导致整个form表单全量渲染。如果仅仅改变name字段,会导致name、age、gender字段全部重新渲染。
import React, { useState } from 'react';
function Form() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [gender, setGender] = useState('');
const handleNameChange = (event) => {
setName(event.target.value);
};
const handleAgeChange = (event) => {
setAge(event.target.value);
};
const handleGenderChange = (event) => {
setGender(event.target.value);
};
return (
<form>
<label>
Name:
<input type="text" value={name} onChange={handleNameChange} />
</label>
<label>
Age:
<input type="number" value={age} onChange={handleAgeChange} />
</label>
<label>
Gender:
<select value={gender} onChange={handleGenderChange}>
<option value="">Select</option>
<option value="male">Male</option>
<option value="female">Female</option>
</select>
</label>
</form>
);
}
useForm能解决以上问题,和上述代码区别在于:
1.不再使用useState来管理状态,useForm提供了register函数,调用该函数可以注册一个字段。因此,'name'、'age'、'gender'三个字段交由'react-hook-form'来管理,
2.点击提交按钮时,触发的是handleSubmit(onSubmit)返回的函数
3.useForm还能实现表单的联动。通过watch监听字段的改变。name的值如果不为空,才会展示age字段。
import React from "react";
import { useForm } from "react-hook-form";
function Form() {
const { register, handleSubmit, watch } = useForm();
const onSubmit = (data) => {
console.log(data);
};
const nameValue = watch("name");
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>
Name:
<input type="text" {...register("name")} />
</label>
{nameValue && (
<div>
<label>
Age:
<input type="number" {...register("age")} />
</label>
</div>
)}
<label>
Gender:
<select {...register("gender")}>
<option value="">Select</option>
<option value="male">Male</option>
<option value="female">Female</option>
</select>
</label>
<button type="submit">Submit</button>
</form>
);
}
参考资料: 1.developer.aliyun.com/article/940…
2.字段的状态需要在业务组件中维护
比如,age和name联动。当name不为空时,才展示age。此时需要在组件中维护showAge这个state。并且每当age改变,会导致整个表单重新渲染。
import React, { useState } from 'react';
import { Form, Input } from 'antd';
const Demo = () => {
const [showAge, setShowAge] = useState(false);
const handleNameChange = (e) => {
if (e.target.value) {
setShowAge(true);
} else {
setShowAge(false);
}
};
return (
<Form>
<Form.Item label="Name" name="name">
<Input onChange={handleNameChange} />
</Form.Item>
{showAge && (
<Form.Item label="Age" name="age">
<Input />
</Form.Item>
)}
</Form>
);
};
export default Demo;
使用步骤
createForm
先回顾一下普通 Antd 中,form实例的作用:用于设置字段的值。
在 formily 中用于定义表单之间的依赖关系,通过 onFieldValueChange() 监听字段变化,然后设置另一个 field 的下拉框内容。
字段的隐藏
不需要useState来管理:
1.代码更加简洁(不需要管理state)
2.性能更高,其余的 filed 不会刷新。(如果用usestate,其余的也会刷新)