formily相关

506 阅读5分钟

1.业务介绍

业务背景

商家有时无法在规定时间内发货,并且他们有正当理由:

  • 1.封控导致快递停运。
  • 2.自然灾害导致交通阻塞。
  • 3.停电等突发情况。
  • 4.重要会议(比如国家级别的)导致快递进不去
  • 等等

在这些情况下,商家并非故意违约,因此平台不应该处罚商家。应提供给商家一个报备页面,提交报备之后,客服进行审核,如果审核通过,则免除对商家的惩罚。

业务需求(已经简化)

(为了叙述方便,简化了业务场景)商家在此表单需要填写:

1.报备原因。可选值有

  • 会议赛事影响
  • 自然灾害影响
  • 停电、网络异常等等

2.受影响地区的类型。可选值有

  • 发货地
  • 收货地

3.受影响地区

  • 如果受影响地区类型是发货地,那么可选择地区只有商家的仓库地址。
  • 如果受影响地区类型是收货地,那么可选择地区是全国各地都可以选。

4.上传凭证

表单的联动规则是:

  1. 首次进入页面,仅仅展示报备原因字段。隐藏受影响地区类型字段。展示受影响地区,但是状态是disable。隐藏上传凭证字段。
  2. 选择报备原因之后,展示受影响地区类型字段。
  3. 受影响地区类型字段和报备原因字段都填写后,才可编辑受影响地区字段。
  4. 改变受影响地区类型,导致受影响地区的下拉列表改变。
  • 如果受影响地区类型是发货地,那么可选择的受影响地区只有商家曾经发货过的地区。
  • 如果受影响地区类型是收货地,那么可选择的受影响地区是全国地区。
  1. 报备原因受影响地区类型受影响地区全部选择后,才会展示上传凭证按钮。

报备表单.gif

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实例的作用:用于设置字段的值。

image.png

在 formily 中用于定义表单之间的依赖关系,通过 onFieldValueChange() 监听字段变化,然后设置另一个 field 的下拉框内容。

image.png

字段的隐藏

不需要useState来管理:

1.代码更加简洁(不需要管理state)

2.性能更高,其余的 filed 不会刷新。(如果用usestate,其余的也会刷新)

image.png