React.cloneElement
下图是React官网的说明
它的作用就是根据一个组件克隆出一个新的组件。
- 第一个参数是原始组件
- 第二个参数是一些新的props,原始组件的props 会和 这个porps浅合并。
- 第三个参数是新组件的children,如果不传会默认使用原始组件的children
demo
import * as React from "react";
const Father = (props) => {
const newChild = React.cloneElement(props.children, { b: "b" });
return (
<>
这是Father组件
{newChild}
</>
);
};
const Child = (props) => {
console.log("child props", props);
return <div>这是Child组件</div>;
};
const App: React.FC = () => {
return (
<>
<Father>
<Child a="a" />
</Father>
</>
);
};
export default App;
在代码中Child只有一个a属性,但是在Father组件中通过React.cloneElement混入了一个b属性,所以实际Child的porps有两个属性
实际业务中的应用
比如我们现在有一组表单组件,并且我们需要实时保存每一项表单的值,就是每一项表单值改变就要保存到后端接口中。(注: 出于性能考虑对于不同的表单组件保存时机是不一样的, 比如Select组件一般是onChange保存,但是对Input组件是需要onBlur是才去保存)
一般我们会这样写,分别去监听组件的事件然后去保存。
import { Form, Input, Select, FormItemProps } from "antd";
import * as React from "react";
import "antd/dist/antd.css";
const {Item } = Form;
export const Index: React.FC = () => {
const [form] = Form.useForm();
const submit = () => {
// 做提交操作
}
return (
<Form
form={form}
style={{ padding: 30, width: 500 }}
>
<Item
name="input"
label="input 输入"
submitTrigger="onBlur"
rules={[{ required: true, max: 6, message: "必填,6个字内" }]}
>
<Input onBlur={submit}/>
</Item>
<Item name="select" label="select 选择">
<Select onChange={submit}>
<Select.Option key="111">111</Select.Option>
<Select.Option key="222">222</Select.Option>
</Select>
</Item>
</Form>
);
};
现在我们用React.cloneElement实现下需求
import { Form, Input, Select, FormItemProps } from "antd";
import * as React from "react";
import "antd/dist/antd.css";
export const Index: React.FC = () => {
const [form] = Form.useForm();
return (
<Form
form={form}
style={{ padding: 30, width: 500 }}
>
<Field
name="input"
label="input 输入"
submitTrigger="onBlur"
rules={[{ required: true, max: 6, message: "必填,6个字内" }]}
>
<Input />
</Field>
<Field name="select" label="select 选择">
<Select>
<Select.Option key="111">111</Select.Option>
<Select.Option key="222">222</Select.Option>
</Select>
</Field>
</Form>
);
};
const Field: React.FC<FormItemProps & { submitTrigger?: string }> = ({
submitTrigger = "onChange",
children,
...rest
}) => {
const form = Form.useFormInstance();
const handleSbumit = () => {
// 提交操作
};
if (!React.isValidElement(children)) {
throw new Error("Form.Item 的children不对");
}
const newChildren = React.cloneElement(children, {
[submitTrigger]: handleSbumit,
});
return <Form.Item {...rest}>{newChildren}</Form.Item>;
};
我们现在的做法是对Form.Item做进一步封装,将子组件提交的时机通过submitTrigger属性在Field组件中进行统一处理。这样做的好处是可以统一处理表单组件,避免代码冗余。对于其他像disabled等需要统一处理属性的也可以这样做。
另外我们知道对于一个自定义组件,只要实现了value,onChange接口就能被Form.Item接管,其实在Form.Item内部也是通过React.cloneElement将value,onChange属性传递给自定义组件的。
// 伪代码
const FormItem = (porps) => {
const value;
const onChange;
const newChild = React.cloneElement(porps.children, {value, onChange});
return <>{newChild}</>
}
const CustomField = ({value,onChange}) => {
//
}
const App = () => {
return <FormItem><CustomField /></FormItem>
}
另外antd中好多组件都是这样处理的
在TabPanelList组件中通过tab.key 和 activeKey就能知道哪个tab是活跃的,并把active属性传递到每个TabPane