React.cloneElement的使⽤详解
⽬录
cloneElement 的作⽤
使⽤场景
添加新的 props
修改 props 的事件
定制样式
添加 key
总结
因为要接⼿维护⼀些项⽬,团队的技术栈最近从 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.
总结下来就是:
1. 克隆原来的元素,返回⼀个新的 React 元素;
2. 保留原始元素的 props,同时可以添加新的 props,两者进⾏浅合并;
3. key 和 ref 会被保留,因为它们本⾝也是 props ,所以也可以修改;
4. 根据 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 = unt(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 Array().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();
pane}
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 。
const ComponentButton = props => {
const { addonAfter, children } = props;
const button = <button key='button'>{ children }</button>
const list = [button, addonAfter ? React.cloneElement(addonAfter, { key: 'button-addon' } : null)
return <div>{ list } <div>
}
总结
在开发复杂组件中,经常会根据需要给⼦组件添加不同的功能或者显⽰效果,react元素本⾝是不可变的 (immutable) 对象,props.children事实上并不是children本⾝,它只是children的描述符 (descriptor) ,我们不能修改任何它的任何属性,只能读到其
中的内容,因此React.cloneElement 允许我们拷贝它的元素,并且修改或者添加新的 props 从⽽达到我们的⽬的。
当然,得益于 react 强⼤的组合模式,这并不仅仅局限于props.children ,不管是props.left 还是props.right 或者任何其它的props传进来的内容,只要是合法的 react 元素,我们都可以使⽤这个React.cloneElement 对其进⾏操作。
以上就是React.cloneElement的使⽤详解的详细内容,更多关于React.cloneElement的使⽤的资料请关注其它相关⽂章!