React的一些高级用法(1)

360 阅读2分钟

React.cloneElement

下图是React官网的说明

L1VzZXJzL3poYW5nZnV5aW5nL0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9EaW5nVGFsa01hYy8yODI1NDcxNjFfdjIvSW1hZ2VGaWxlcy8xNjU4OTA2ODYwMzk3Xzg4QzUyQjUxLUUyRkEtNDY1Ri1CODVCLTE2RTk0NjYyRjlBQS5wbmc=.png

它的作用就是根据一个组件克隆出一个新的组件。

  • 第一个参数是原始组件
  • 第二个参数是一些新的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;

L1VzZXJzL3poYW5nZnV5aW5nL0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9EaW5nVGFsa01hYy8yODI1NDcxNjFfdjIvSW1hZ2VGaWxlcy8xNjU4OTA4MTkyOTMxXzlFM0EwQjFELTE4ODQtNDEyRS1BNzRFLTVEMEZDNjI1NUM3OC5wbmc=.png 在代码中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中好多组件都是这样处理的

L1VzZXJzL3poYW5nZnV5aW5nL0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9EaW5nVGFsa01hYy8yODI1NDcxNjFfdjIvSW1hZ2VGaWxlcy8xNjU4OTEzMDYzMzc4X0MxRjU5RkMwLUMxODctNDE1Ni04OTlELTNBQUMwRDFCNEI5OC5wbmc=.png 在TabPanelList组件中通过tab.key 和 activeKey就能知道哪个tab是活跃的,并把active属性传递到每个TabPane

总结: 通过React.cloneElement向子组件混入一些props可以让父组件劫持控制子组件