不要疯狂提交表单呀,混蛋

182 阅读4分钟

前端在完成表单的时候常常需要做这样的功能:

  1. 当表单中的内容变化的时候 save 按钮才可以点击,否则应该是置灰的。
  2. 这个功能的生效时机应该是可人为控制的。

这种需求见的多了,每一个都去手写一遍,不够优雅。那么今天就统一处理一下这个问题好了。

image.png

1. hook 封装

为了完成这个功能,我们可以封装一个自定义Hook,名为useFormButtonState。这个Hook将提供一个状态来控制save按钮的可点击状态,以及一个函数来手动更新这个状态。

实现代码如下:

import { useState, useCallback } from 'react';

function useFormButtonState(initialDirty = false) {
  const [isDirty, setIsDirty] = useState(initialDirty);

  const makeDirty = useCallback(() => {
    setIsDirty(true);
  }, []);

  const makeClean = useCallback(() => {
    setIsDirty(false);
  }, []);

  const setDirtyState = useCallback((newState) => {
    setIsDirty(newState);
  }, []);

  return [isDirty, makeDirty, makeClean, setDirtyState];
}

export default useFormButtonState;

hook 的使用步骤:

  1. 在你的组件中引入并使用这个Hook。
  2. isDirty状态绑定到save按钮的disabled属性上。
  3. 在表单内容发生变化时,调用makeDirty函数来启用save按钮。
  4. 如果需要,你也可以在适当的时候调用makeClean函数来禁用save按钮。
  5. 如果你想更细粒度地控制按钮状态,可以直接使用setDirtyState函数。

下面是此 hook 的简单使用示例:

import React, { useState } from 'react';
import useFormButtonState from './useFormButtonState';

function MyForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [isDirty, makeDirty, makeClean, setDirtyState] = useFormButtonState();

  const handleNameChange = (event) => {
    setName(event.target.value);
    makeDirty(); // 启用save按钮,因为表单内容已更改
  };

  const handleEmailChange = (event) => {
    setEmail(event.target.value);
    makeDirty(); // 启用save按钮,因为表单内容已更改
  };

  const handleSave = () => {
    // 保存表单数据的逻辑...
    makeClean(); // 保存后禁用save按钮,因为表单内容未更改
  };

  return (
    <form>
      <input type="text" value={name} onChange={handleNameChange} placeholder="Name" />
      <input type="email" value={email} onChange={handleEmailChange} placeholder="Email" />
      <button disabled={!isDirty} onClick={handleSave}>Save</button>
    </form>
  );
}

2. 简单场景使用

但是我们一般不会去手写 form 表单的。如果使用的是 Ant Design 的 Form 组件,可以在 Form 组件的 onValuesChange 事件中调用 makeDirty 函数。这样,每当表单中的任何字段值发生变化时,都会触发该函数,从而将 Save 按钮设置为可点击状态。

以下是如何在 Ant Design 的 Form 组件中使用 useFormButtonState Hook 的示例:

import React from 'react';
import { Form, Input, Button } from 'antd';
import useFormButtonState from './useFormButtonState';

const MyForm = () => {
  const [form] = Form.useForm();
  const [isDirty, makeDirty, makeClean] = useFormButtonState();

  const onFormChange = (changedValues, allValues) => {
    makeDirty(); // 当表单值发生变化时,将按钮设置为可点击状态
  };

  const onFinish = (values) => {
    console.log('Received values of form:', values);
    // 处理表单提交逻辑...
    makeClean(); // 提交表单后,将按钮设置为不可点击状态
  };

  return (
    <Form
      form={form}
      onValuesChange={onFormChange}
      onFinish={onFinish}
    >
      <Form.Item
        name="username"
        rules={[{ required: true, message: 'Please input your username!' }]}
      >
        <Input placeholder="Username" />
      </Form.Item>

      <Form.Item>
        <Button type="primary" htmlType="submit" disabled={!isDirty}>
          Save
        </Button>
      </Form.Item>
    </Form>
  );
};

export default MyForm;

在这个示例中,onFormChange 函数会在表单中的任何字段值发生变化时被调用,并触发 makeDirty,从而将 Save 按钮设置为可点击。当表单提交成功并调用 onFinish 函数后,可以通过 makeClean 将 Save 按钮设置为不可点击状态,表示表单已经保存并且当前没有未保存的更改。

image.png image.png

3. 复杂场景使用

现在考虑在比较复杂的情况下阻止 makeDirty() 的执行,比如当表单数据是从外部传入并用 setFieldsValue 填充时,可以在组件内部使用一个状态或标志来控制是否应该调用 makeDirty()

以下示例用来说明如何在组件中添加一个状态来控制 makeDirty() 的执行:

import React, { useEffect, useState } from 'react';
import { Form, Input, Button } from 'antd';
import useFormButtonState from './useFormButtonState';

const MyForm = ({ initialValues }) => {
  const [form] = Form.useForm();
  const [isDirty, makeDirty, makeClean, setDirtyState] = useFormButtonState();
  const [preventMakeDirty, setPreventMakeDirty] = useState(true); // 添加一个状态来控制makeDirty的执行

  useEffect(() => {
    // 设置初始值,并在此过程中防止makeDirty()被调用
    setPreventMakeDirty(true);
    form.setFieldsValue(initialValues);
    setPreventMakeDirty(false); // 设置完初始值后,允许makeDirty()被调用
  }, [initialValues, form]);

  const onFormChange = (changedValues, allValues) => {
    if (!preventMakeDirty) {
      makeDirty(); // 只有当preventMakeDirty为false时,才调用makeDirty()
    }
  };

  const onFinish = (values) => {
    console.log('Received values of form:', values);
    // 处理表单提交逻辑...
    makeClean(); // 提交表单后,重置按钮状态
  };

  return (
    <Form
      form={form}
      onValuesChange={onFormChange}
      onFinish={onFinish}
      initialValues={initialValues}
    >
      {/* ... 其他表单项 ... */}
      <Form.Item>
        <Button type="primary" htmlType="submit" disabled={!isDirty}>
          Save
        </Button>
      </Form.Item>
    </Form>
  );
};

export default MyForm;

在这个示例中,我添加了一个名为 preventMakeDirty 的状态,它会在组件挂载时使用 useEffect 设置表单初始值时被设置为 true,然后在设置完初始值后再设置为 false。这样,在 onFormChange 中,我们可以检查 preventMakeDirty 的状态,只有当它为 false 时,才会调用 makeDirty()

这种方法允许您在特定情况下(如设置表单初始值)阻止 makeDirty() 的执行,而在其他表单值变化时则正常调用 makeDirty()

4. ProForm 中的使用

如果你使用的是 antd component pro 中的 ProForm 作为表单元素,那么可以按照如下代码设置此功能:

  submitter={{
    searchConfig: {
      submitText: 'Save',
      resetText: 'Reset',
    },
    render: (_, dom) => {
      return (
      <Space className="form-footer">
        {dom[0]}
        <Button type="primary" disabled={isEdit && !isDirty} onClick={handleSubmit}>
          Save
        </Button>
      </Space>
    )},
  }}

啊啊啊啊!千万不要写成上面那样!!! onClick={handleSubmit} 是绝对不被允许的!

  submitter={{
    searchConfig: {
      submitText: 'Save',
      resetText: 'Reset',
    },
    render: (_, dom) => {
      return (
        <Space className="app-hub-edit-form-footer">
          {dom[0]}
          <Button type="primary" disabled={isEdit && !isDirty} onClick={() => {
            formRef.current?.submit()
          }}>
            Save
          </Button>
        </Space>
      )
    },
  }}

如果您觉得这篇文章对您的开发有所帮助,请点一个赞吧,非常感谢!❤