前端在完成表单的时候常常需要做这样的功能:
- 当表单中的内容变化的时候 save 按钮才可以点击,否则应该是置灰的。
- 这个功能的生效时机应该是可人为控制的。
这种需求见的多了,每一个都去手写一遍,不够优雅。那么今天就统一处理一下这个问题好了。
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 的使用步骤:
- 在你的组件中引入并使用这个Hook。
- 将
isDirty状态绑定到save按钮的disabled属性上。 - 在表单内容发生变化时,调用
makeDirty函数来启用save按钮。 - 如果需要,你也可以在适当的时候调用
makeClean函数来禁用save按钮。 - 如果你想更细粒度地控制按钮状态,可以直接使用
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 按钮设置为不可点击状态,表示表单已经保存并且当前没有未保存的更改。
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>
)
},
}}
如果您觉得这篇文章对您的开发有所帮助,请点一个赞吧,非常感谢!❤