React使用cloneElement对组件进行隐式传递props

396 阅读2分钟

背景

在开发react项目时最常用的组件库就是蚂蚁开源的antd,在最近的聊天中,有一位同学提到在使用antd的Form组件和FormItem组件的时候,明明自己没有给FormItem包裹的组件传递props,但是却可以在组件中收到,并对此感到很迷惑。

我也回想起了我第一次使用antd开发时也遇到了这种问题,因此就专门花时间翻阅了一波源码,然后便瞬间豁然开朗了。

本文就对个问题以及antd如何实现的作一个总结

为什么会有 cloneElement 这个Api

首先我们要了解React.cloneElement这个api的作用有一个初步的了解。 考虑以下代码,如果我们想要给Bar中传递一个color的props,但是Bar已经确定了,那该如何传递

import React, { FC, ReactElement } from "react";

const App = () => {
  return (
    <Foo>
      <Bar name="bar" />
    </Foo>
  );
};

const Foo: FC<{ children: ReactElement }> = ({children}) => {
  return <>{children}</>;
};
const Bar = (props:any) => {
  console.log(props) // now: { name:"bar" } =>expect: {name:"bar",color:"red"}
  return <>Bar</>;
};
export default App;

在这种情况下Bar组件已经完成初始化,是无法对Bar传递props的,但是很多封装的组件库也考虑到了这个问题,这些参数不可能让用户进行传递,这样对用户的心智负担是比较大的,于是就有了今天我们介绍的Api cloneElement,首先我们先从cloneElement的类型标注上来了解这个函数.

cloneElement 的理解

image.png

可以看到cloneElement的类型重载还是比较多的,但都是三个参数,第一个参数element是你想要clone的元素,第二个组件是它的props,第三个组件是它的children。

这样一看是不是很像createElement,但其实他们两个是差不多的,就如同函数名字一样createElement是创造一个Element,而cloneElement是cloneElement并且可以再次向这个Element注入props等。 就比如说上面那个例子,我们想要往元素中混入color就可以使用cloneElement这个Api

import React, { FC, ReactElement, ReactNode, cloneElement } from "react";

const App = () => {
  return (
    <Foo>
      <Bar name="bar" />
    </Foo>
  );
};

const Foo: FC<{ children: ReactElement }> = ({ children }) => {
  const CBar = cloneElement(children, { color: "red" });
  return <>{CBar}</>;
};
const Bar = (props: any) => {
  console.log(props); //{name: 'bar', color: 'red'}
  
  return <>Bar</>;
};
export default App;

antd源码揭秘

在FormItem这个组件中,也用到了cloneElement这个函数将组件的props进行混入

image.png

混入的props有很多,包括一些事件等,这也就是为什么我们为什么没传递但能收到onChange事件的原因。

image.png