React.cloneElement的利用详解

因为要接办维护一些项目,团队的技能栈最近从 vue 转向 react ,作为一个 react 新手,加上一向喜欢通过源码来进修新的对象,就选择了通过阅读 antd 这个台甫鼎鼎的项目源码来进修一些 react 的用法。

在阅读源码的进程中,发明好些组件都利用了 React.cloneElement 这个 api ,固然通过名字可以揣摩它做了什么,可是并不知道详细的浸染;然后去看官方文档,文档很清晰地描写了它的浸染,却没有汇报我们什么场景下需要利用它。于是我按照文档的描写,团结源码的利用,面向 google 和 stackoverflow,总结出来一些利用场景。

cloneElement 的浸染

React.cloneElement( element, [props], [...children] )

首先看一下官方文档对这个 API 的描写:

Clone and return a new React element using element as the starting point. The resulting element will have the original element's props with the new props merged in shallowly. New children will replace existing children. key and ref from the original element will be preserved.

总结下来就是:

克隆本来的元素,返回一个新的 React 元素;

保存原始元素的 props,同时可以添加新的 props,两者举办浅归并;

key 和 ref 会被保存,因为它们自己也是 props ,所以也可以修改;

按照 react 的源码,我们可以从第三个参数开始界说任意多的子元素,假如界说了新的 children ,会替换本来的 children ;

利用场景

按照上面的界说解析,我们可以在差异的场景下按照需要来利用这个 api 。

添加新的 props

当我们建设一个通用组件时,按照内部的逻辑,想要给每个子元素添加差异的类名,这个时候我们可以修改它的 className :

假设我们有一个 Timeline 组件,答允我们按照需要界说多个 TimelineItem ,在内部我们想要给最后一个TimelineItem 添加一个 timeline-item-last 类来渲染非凡的结果,这个时候我们可以这样做:

const MyTimeline = () => { return ( <Timeline> <TimelineItem>2020-06-01</TimelineItem> <TimelineItem>2020-06-08</TimelineItem> <TimelineItem>2020-07-05</TimelineItem> </Timeline> ) } // 在 Timeline 内部,逻辑大概是这样的 import class from 'classnames'; const Timeline = props => { // ... // ... const itemCount = React.children.count(props.children); const items = React.children.map(props.children, (item, index) => { return React.cloneElement(item, { className: class([ item.props.className, 'timeline-item', index === count - 1 ? 'timeline-item-last' : '' ]) }) } return <div className={'timeline'}>{ items }</div> }

除了添加 className ,还可以动态给子组件添加更多的 props 信息,react-router 的 Switch 会给匹配的子组件添加 location 和 computedMatch 信息:

class Switch extends React.Component { render() { return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Switch> outside a <Router>"); const location = this.props.location || context.location; let element, match; // We use React.Children.forEach instead of React.Children.toArray().find() // here because toArray adds keys to all child elements and we do not want // to trigger an unmount/remount for two <Route>s that render the same // component at different URLs. React.Children.forEach(this.props.children, child => { if (match == null && React.isValidElement(child)) { element = child; const path = child.props.path || child.props.from; match = path ? matchPath(location.pathname, { ...child.props, path }) : context.match; } }); return match ? React.cloneElement(element, { location, computedMatch: match }) : null; }} </RouterContext.Consumer> ); } }

修改 props 的事件

假设我们有一个 Tab 组件,它下面包括多个 TabPane 子组件,我们想要点击每个 TabPane 子组件的同时触发 Tab 的 onClick 事件,用户本身自己大概给每个 TabPane 界说了独立的 onClick 事件,这时候我们就要修改子组件 onClick 事件:

const Tab = props => { const { onClick } = props; const tabPanes = React.children.map(props.children, (tabPane, index) => { const paneClick = () => { onClick && onClick(index); tabPane.props?.onClick(); } return React.cloneElement(tabPane, { onClick: paneClick, }) }) return <div>{ tabPanes }</div> }

定制样式

建设一个叫 FollowMouse 组件时,我们答允用户界说内容组件 Content ,当鼠标移动时,按照内容的巨细,自动计较 Content 的位置制止溢出屏幕,这个时候我们就可以利用 cloneElement 来动态修改它的样式。

// 简朴起见,这里省略鼠标事件。 const FollowMouse = props => { const { Content } = props; const customContent = React.isValidElement ? Content : <span>{ Content }</span> const getOffset = () => { return { position: 'absolute', top: ..., left: ..., } } const renderContent = React.cloneElement(custonContent, { style: { ...getOffset() } }) return <div>{ renderContent() }</div> }

添加 key

当我们建设一个元素列表时,可以通过 cloneElement 给每个节点添加一个 key 。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsjyzy.html