React中嵌套组件与被嵌套组件的通信过程
前言
在React项目的开发中经常会遇到这样一个场景:嵌套组件与被嵌套组件的通信。
比如Tab组件啊,或者下拉框组件。
场景
这里应用一个最简单的Tab组件来呈现这个场景。
importReact,{Component,PropTypes}from'react' classTabextendsComponent{ staticpropTypes={ children:PropTypes.node } render(){ return(
-
{this.props.children}
这里有Tab,TabItem和Area三个组件,其中Tab为嵌套组件,TabItem为被嵌套组件,Area为使用它们的组件。
在上述场景中,点击哪个TabItem项时,就将这个TabItem项激活。
以上方案算是嵌套组件最常用的方案了。
需求的变更与缺陷的暴露
在上述场景下应用上述方案是没有问题的,但是我们通常用的Tab没有这么简单,比如当点击武汉这个TabItem时,武汉地区的美食也要展示出来。
这种场景下就需要修改TabItem组件为:
classTabItemextendsComponent{ staticpropTypes={ name:PropTypes.string, active:PropTypes.bool, onClick:PropTypes.func, children:PropTypes.node } handleClick=()=>{ this.props.onClick(this.props.name) } render(){ return({this.props.name} {this.props.children}
然后沿用上述方案,那么就需要改变Area组件为:
exportdefaultclassAreaextendsComponent{ state={ activeName:'' } handleClick=(name)=>{ this.setState({ activeName:name }) } render(){ return() } } 武汉的美食,这里有一大堆jsx代码 武汉的美食,这里有一大堆jsx代码 武汉的美食,这里有一大堆jsx代码
这里的Area使用TabItem的时候已经没办法用数组+map的形式去写了。
因为这里有大量的jsx在这里,如果那样去写,代码的可读性将会非常糟糕。
那么用上面的写法写的时候,就会出现一个问题,就是onClick在不断重复,active的判断也在不断重复。
尝试掩盖active判断重复的问题
这个比较容易,修改代码如下:
classTabItemextendsComponent{ staticpropTypes={ name:PropTypes.string, activeName:PropTypes.string, onClick:PropTypes.func, children:PropTypes.node } handleClick=()=>{ this.props.onClick(this.props.name) } render(){ return({this.props.name} {this.props.children}
尝试掩盖onClick不断重复的问题
想要onClick不重复,那么就不能将其写在TabItem上,而是应该写在Tab上。
那么这个地方就得用到事件冒泡的机制。
将onClick写在Tab上,然后根据捕获的事件消息,获取target的class是否为switchBtn,然后得到target的text。
再将这个text赋值为activeName。
并且你还得期望点击的switchBtn的内的结构不那么复杂,最好是就只有一个文本。
如果需求还要给Tab项的切换按钮每个都加上图标,那么你还得看这个事件的target是不是这个图标。那么又需要做更多的处理了。
想一想就觉得麻烦。
一般在这种情况下,脑子里唯一的想法就是,就这样吧,这个onClick重复就重复吧,没什么大不了的。
连我自己都懒得写这部分代码了。
嵌套组件与被嵌套组件的通信:React.Children与React.cloneElement
实际上要解决上面的问题,只需要一个东西就好了,那就是嵌套组件能传递值给被嵌套组件的props,比如onClick。
那么先上一份代码吧。
classTabItemextendsComponent{ staticpropTypes={ name:PropTypes.string, activeName:PropTypes.string, onClick:PropTypes.func, children:PropTypes.node } handleClick=()=>{ this.props.onClick(this.props.name) } render(){ return({this.props.name} {this.props.children}