我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!
写在前面
- 前面几篇我们已经仿写实现了 rc-field-form 的大部分功能了
- 在之前的实现中,我们在 Form 组件之外实现了一个数据仓库,并且通过自定义 hook useForm 将数据仓库实例的控制权限对象返回出去,这样我们需要操作数据仓库时,只用调用 useForm 就能拿到数据仓库的控制权限对象了
- 然后我们通过 React 提供的 Context API 将数据仓库的控制权限,从 Form 组件顶层跨层级传递给它的子孙组件 Field 组件进行消费使用
- 大家有没有发现,这种方案只能在 Form 组件内部及其子孙组件中才能操作数据仓库,那我们如何在 Form 组件外部操作数据仓库呢,例如:如何在使用 Form 组件的父组件挂载后,给 Form 组件的某个 Field 设置一个默认值?
- 今天来实现一下这个需求
在函数组件中使用 Form 组件
- 我们先来看下代码
export const FuncMyRCFormPage = () => {
const [form] = Form.useForm();
const onFinish = (val) => console.log("onFinish", val);
const onFinishFailed = (val) => console.log("onFinishFailed", val);
useEffect(() => {
form.setFieldsValue({ user: "hook form default" });
}, []);
return (
<div style={{ width: 500, margin: "auto" }}>
<h2>func-my-rc-field-formPage</h2>
<Form form={form} onFinish={onFinish} onFinishFailed={onFinishFailed}>
<Field name="user" label="账号" rules={userRules}>
<Input placeholder="请输入账号" />
</Field>
<Field name="pwd" label="密码" rules={pwdRules}>
<Input placeholder="请输入密码" />
</Field>
<button>Submit</button>
</Form>
</div>
);
};
- 既然是函数组件,那么理所当然的我们可以使用 useForm 自定义 hook 来获取数据仓库的权限对象
- 那么这样就会出现一个问题,怎么保证 FuncMyRCFormPage 这个组件使用的数据仓库权限对象,就是我们 Form 组件使用数据仓库权限对象呢?
- 我们可以改造一下 useForm 的实现,代码如下
export function useForm(formStoreApi) {
const formRef = useRef();
if (!formRef.current) {
if (formStoreApi) {
formRef.current = formStoreApi;
} else {
const formStore = new FormStore();
formRef.current = formStore.getForm();
}
}
return [formRef.current];
}
- useForm 可以接收一个数据仓库的操作权限对象 formStoreApi
- 如果 formRef 对象已经保存了数据仓库操作权限对象,那就可以直接返回它
- 否则,进行数据仓库操作权限对象的初始化操作
- 如果有外部传入的权限对象,就用外部传入的
- 如果外部没有传入就实例化一个新的数据仓库权限对象
- 这样 FuncMyRCFormPage 如果使用 useForm 初始化了一个数据仓库的操作权限对象,就可以直接通过 props 传给 Form 组件
- 同样的 Form 组件也需要做一些改写
export default function Form(
{ children, form, onFinishFailed, onFinish }
) {
const [formStore] = useForm(form);
// 注册 Form 上的回掉函数,数据校验后,提交数据时调用
formStore.setCallbacks({
onFinish,
onFinishFailed,
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
formStore.submit();
}}
>
<FormContext.Provider value={formStore}>{children}</FormContext.Provider>
</form>
);
}
- 只需要将接收的 props 中的数据仓库操作权限对象,也就是上面的 form,传给 useForm 即可
在类组件中使用 Form 组件
- 上面函数组件可以使用自定义 hook,那么类组件该怎么拿到数据仓库的操作权限对象呢
- 先让我们看看类组件的使用
export class ClassMyRCFormPage extends Component {
formRef = React.createRef();
componentDidMount() {
this.formRef.current.setFieldsValue({ user: "class form default" });
}
onFinish = (val) => console.log("onFinish", val);
onFinishFailed = (val) => console.log("onFinishFailed", val);
render() {
return (
<div style={{ width: 500, margin: "auto" }}>
<h2>class-my-rc-field-formPage</h2>
<Form
ref={this.formRef}
onFinish={this.onFinish}
onFinishFailed={this.onFinishFailed}
>
<Field name="user" label="账号" rules={userRules}>
<Input placeholder="请输入账号" />
</Field>
<Field name="pwd" label="密码" rules={pwdRules}>
<Input placeholder="请输入密码" />
</Field>
<button>Submit</button>
</Form>
</div>
);
}
}
- 类组件确实无法使用自定义 hook,但是我们依然可以在 ClassMyRCFormPage 组件中使用 ref 去拿到其子组件 Form 中初始化的数据仓库操作权限对象
- 但是我们的 Form 组件是函数组件,无法直接接收 ref,那该怎么办呢?
- 好在 react 官网文档已经说了解决方案,下面是具体的解决方案代码
import React from "react";
import _Form from "./Form";
import { useForm } from "./useForm";
const Form = React.forwardRef(_Form);
Form.useForm = useForm;
export { Form };
export * from "./Input";
export * from "./Field";
- 在组件导出时,使用 React.forwardRef 进行一次 ref 的转发,这样我们的 Form 组件就能接收到 ref 了
- 然后再对我们的 Form 组件做一些修改
export default function Form(
{ children, form, onFinishFailed, onFinish },
ref // 使用 forwardRef,将 Form 接收的 ref 进行转发
) {
const [formStore] = useForm(form);
// 通过 useImperativeHandle hook 将 formStore 暴露给父组件
React.useImperativeHandle(ref, () => formStore);
// 注册 Form 上的回掉函数,数据校验后,提交数据时调用
formStore.setCallbacks({
onFinish,
onFinishFailed,
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
formStore.submit();
}}
>
<FormContext.Provider value={formStore}>{children}</FormContext.Provider>
</form>
);
}
- 可以看到上面的代码中,使用了一个不咋常用的 hook api: useImperativeHandle
- 它通常和 React.forwardRef 一起搭配使用,可以让我们在使用 ref 时自定义想要暴露给父组件的实例属性
- 至此,我们仿写 rc-field-form 的所有需求就全部完成了
最后
- 今天的分享就到这里了,欢迎大家在评论区里面进行讨论 👏。
- 如果觉得文章写的不错的话,希望大家不要吝惜点赞,大家的鼓励是我分享的最大动力 🥰