React+Ant Design:切换Tabs前提交表单的非命令式实现方案
问题
我用Ant Design的Tabs组件,每个Tab页嵌入一个Ant Design表单。需求是:切换Tab时,提示用户提交表单更改;若用户选择提交,需提交成功后再切换Tab;若选择不保存,则直接切换。
表单都是子组件,所以父组件需要通知子组件Tab即将切换,并触发表单提交。我已经用useImperativeHandle和forwardRef实现了这个功能,但想知道有没有非命令式的实现方式?
现有实现代码
表单组件(MyForm.tsx)
export type FormRef = { submit: (data?: Data) => Promise<boolean> }; export type Data = { someField: string }; const MyForm = (props: {}, ref: Ref<FormRef>) => { const [form] = useForm<Data>(); useImperativeHandle(ref, () => ({ submit })); async function submit(data?: Data): Promise<boolean> { if (!data) data = form.getFieldsValue(); const res = await submitData(data); return res; } return ( <Form form={form} onFinish={submit}> <Form.Item name="someField"> <Input /> </Form.Item> <Form.Item> <Button htmlType="submit">Submit</Button> </Form.Item> </Form> ); }; export default forwardRef(MyForm);
父组件(App.tsx)
const App: FC = () => { const [activeKey, setActiveKey] = useState('tabOne'); const formOneRef = useRef<FormRef>(); const formTwoRef = useRef<FormRef>(); async function onChange(key: string) { if (confirm('Save Changes?')) { if (activeKey === 'tabOne' && (await formOneRef.current.submit())) setActiveKey(key); if (activeKey === 'tabTwo' && (await formTwoRef.current.submit())) setActiveKey(key); } else setActiveKey(key); } const tabs = [ { label: 'Tab One', key: 'tabOne', children: <MyForm ref={formOneRef} />, }, { label: 'Tab Two', key: 'tabTwo', children: <MyForm ref={formTwoRef} />, }, ]; return <Tabs items={tabs} onChange={onChange} activeKey={activeKey} />; };
提交函数(submitData.ts)
export default async function submitData(data: Data) { console.log('Submitting...', data); const res = await new Promise<boolean>((resolve) => setTimeout( () => (Math.random() < 0.5 ? resolve(true) : resolve(false)), 1000 ) ); if (res) { console.log('Success!', data); return true; } if (!res) { console.error('Fake Error', data); return false; } }
非命令式实现方案
核心思路是状态提升+回调传递,通过props建立父子组件的单向数据流通信,完全避免ref的命令式调用:
1. 改造表单组件
让表单组件接收onRegisterSubmitter回调,将自身的提交方法传递给父组件;同时保留内部的表单提交逻辑。
export type Data = { someField: string }; type MyFormProps = { onRegisterSubmitter: (submitter: () => Promise<boolean>) => void; }; const MyForm = ({ onRegisterSubmitter }: MyFormProps) => { const [form] = useForm<Data>(); async function submit(data?: Data): Promise<boolean> { if (!data) data = form.getFieldsValue(); const res = await submitData(data); return res; } // 组件挂载时向父组件注册提交方法 useEffect(() => { onRegisterSubmitter(submit); }, [onRegisterSubmitter, submit]); return ( <Form form={form} onFinish={submit}> <Form.Item name="someField"> <Input /> </Form.Item> <Form.Item> <Button htmlType="submit">Submit</Button> </Form.Item> </Form> ); }; export default MyForm;
2. 改造父组件
父组件维护所有表单的提交方法映射,在Tab切换时调用当前激活Tab对应表单的提交逻辑,根据结果决定是否切换Tab:
const App: FC = () => { const [activeKey, setActiveKey] = useState('tabOne'); // 存储每个Tab对应的表单提交方法 const [submitters, setSubmitters] = useState<Record<string, () => Promise<boolean>>>({}); // 注册单个表单的提交方法 const registerSubmitter = (tabKey: string, submitter: () => Promise<boolean>) => { setSubmitters(prev => ({ ...prev, [tabKey]: submitter })); }; async function onChange(key: string) { if (confirm('Save Changes?')) { const currentSubmitter = submitters[activeKey]; if (currentSubmitter && (await currentSubmitter())) { setActiveKey(key); } } else { setActiveKey(key); } } const tabs = [ { label: 'Tab One', key: 'tabOne', children: <MyForm onRegisterSubmitter={(submitter) => registerSubmitter('tabOne', submitter)} />, }, { label: 'Tab Two', key: 'tabTwo', children: <MyForm onRegisterSubmitter={(submitter) => registerSubmitter('tabTwo', submitter)} />, }, ]; return <Tabs items={tabs} onChange={onChange} activeKey={activeKey} />; };
方案优势
- 完全遵循React单向数据流理念,组件间依赖关系清晰
- 避免了ref命令式调用带来的耦合问题
- 扩展性强,后续若需添加表单修改状态判断,只需在子组件中新增逻辑并通过props传递给父组件即可
内容的提问来源于stack exchange,提问作者Chris Hamilton
相关产品推荐
相关产品推荐

