React中嵌套组件与被嵌套组件的通信过程
前言
在react项目的开发中经常会遇到这样一个场景:嵌套组件与被嵌套组件的通信。
比如tab组件啊,或者下拉框组件。
场景
这里应用一个最简单的tab组件来呈现这个场景。
import react, { component, proptypes } from 'react' class tab extends component { static proptypes = { children: proptypes.node } render() { return ( <ul> {this.props.children} </ul> ) } } class tabitem extends component { static proptypes = { name: proptypes.string, active: proptypes.bool, onclick: proptypes.func } handleclick = () => { this.props.onclick(this.props.name) } render() { return ( <li onclick={this.handleclick} classname={this.props.active ? 'active' : 'noactive'}> {this.props.name} </li> ) } } export default class area extends component { state = { activename: '' } handleclick = (name) => { this.setstate({ activename: name }) } render() { return ( <tab> {['武汉', '上海', '北京'].map((item) => <tabitem onclick={this.handleclick} active={this.state.activename === item} name={item} />)} </tab> ) } }
这里有tab,tabitem和area三个组件,其中tab为嵌套组件,tabitem为被嵌套组件,area为使用它们的组件。
在上述场景中,点击哪个tabitem项时,就将这个tabitem项激活。
以上方案算是嵌套组件最常用的方案了。
需求的变更与缺陷的暴露
在上述场景下应用上述方案是没有问题的,但是我们通常用的tab没有这么简单,比如当点击武汉这个tabitem时,武汉地区的美食也要展示出来。
这种场景下就需要修改tabitem组件为:
class tabitem extends component { static proptypes = { name: proptypes.string, active: proptypes.bool, onclick: proptypes.func, children: proptypes.node } handleclick = () => { this.props.onclick(this.props.name) } render() { return ( <li onclick={this.handleclick} classname={this.props.active ? 'active' : 'noactive'}> <span classname='switchbtn'>{this.props.name}</span> <div classname={this.props.active ? 'show' : 'hide'}> {this.props.children} </div> </li> ) } }
然后沿用上述方案,那么就需要改变area组件为:
export default class area extends component { state = { activename: '' } handleclick = (name) => { this.setstate({ activename: name }) } render() { return ( <tab> <tabitem onclick={this.handleclick} active={this.state.activename === '武汉'} name={'武汉'} > 武汉的美食,这里有一大堆jsx代码 </tabitem> <tabitem onclick={this.handleclick} active={this.state.activename === '上海'} name={'上海'} > 武汉的美食,这里有一大堆jsx代码 </tabitem> <tabitem onclick={this.handleclick} active={this.state.activename === '北京'} name={'北京'} > 武汉的美食,这里有一大堆jsx代码 </tabitem> </tab> ) } }
这里的area使用tabitem的时候已经没办法用 数组+map 的形式去写了。
因为这里有大量的jsx在这里,如果那样去写,代码的可读性将会非常糟糕。
那么用上面的写法写的时候,就会出现一个问题,就是onclick在不断重复,active的判断也在不断重复。
尝试掩盖active判断重复的问题
这个比较容易,修改代码如下:
class tabitem extends component { static proptypes = { name: proptypes.string, activename: proptypes.string, onclick: proptypes.func, children: proptypes.node } handleclick = () => { this.props.onclick(this.props.name) } render() { return ( <li onclick={this.handleclick} classname={this.props.activename === this.props.name ? 'active' : 'noactive'}> <span classname='switchbtn'>{this.props.name}</span> <div classname={this.props.active ? 'show' : 'hide'}> {this.props.children} </div> </li> ) } } export default class area extends component { state = { activename: '' } handleclick = (name) => { this.setstate({ activename: name }) } render() { return ( <tab> <tabitem onclick={this.handleclick} activename={this.state.activename} name={'武汉'} > 武汉的美食,这里有一大堆jsx代码 </tabitem> <tabitem onclick={this.handleclick} activename={this.state.activename} name={'上海'} > 武汉的美食,这里有一大堆jsx代码 </tabitem> <tabitem onclick={this.handleclick} activename={this.state.activename} name={'北京'} > 武汉的美食,这里有一大堆jsx代码 </tabitem> </tab> ) } }
尝试掩盖onclick不断重复的问题
想要onclick不重复,那么就不能将其写在tabitem上,而是应该写在tab上。
那么这个地方就得用到事件冒泡的机制。
将onclick写在tab上,然后根据捕获的事件消息,获取target的class是否为switchbtn,然后得到target的text。
再将这个text赋值为activename。
并且你还得期望点击的switchbtn的内的结构不那么复杂,最好是就只有一个文本。
如果需求还要给tab项的切换按钮每个都加上图标,那么你还得看这个事件的target是不是这个图标。那么又需要做更多的处理了。
想一想就觉得麻烦。
一般在这种情况下,脑子里唯一的想法就是,就这样吧,这个onclick重复就重复吧,没什么大不了的。
连我自己都懒得写这部分代码了。
嵌套组件与被嵌套组件的通信:react.children与react.cloneelement
实际上要解决上面的问题,只需要一个东西就好了,那就是嵌套组件能传递值给被嵌套组件的props,比如onclick。
那么先上一份代码吧。
class tabitem extends component { static proptypes = { name: proptypes.string, activename: proptypes.string, onclick: proptypes.func, children: proptypes.node } handleclick = () => { this.props.onclick(this.props.name) } render() { return ( <li onclick={this.handleclick} classname={this.props.activename === this.props.name ? 'active' : 'noactive'}> <span classname='switchbtn'>{this.props.name}</span> <div classname={this.props.active ? 'show' : 'hide'}> {this.props.children} </div> </li> ) } } class tab extends component { static proptypes = { children: proptypes.node, onclickitem: proptypes.func, activename: proptypes.string } render() { return ( <ul> { react.children.map(this.props.children,(child)=>{ if (child.type === tabitem) { return react.cloneelement(child, { // 把父组件的props.name赋值给每个子组件(父组件传值给子组件) activename: this.props.activename, // 父组件的方法挂载到props.onclick上,以便子组件内部通过props调用 onclick: this.props.onclickitem }) } else { return child } }) } </ul> ) } } export default class area extends component { state = { activename: '' } handleclick = (name) => { this.setstate({ activename: name }) } render() { return ( <tab activename={this.state.activename} onclick={this.handleclick} > <tabitem name={'武汉'} > 武汉的美食,这里有一大堆jsx代码 </tabitem> <tabitem name={'上海'} > 武汉的美食,这里有一大堆jsx代码 </tabitem> <tabitem name={'北京'} > 武汉的美食,这里有一大堆jsx代码 </tabitem> </tab> ) } }
通过这种方式,我们发现在使用tab和tabitem时会变得非常简单。
那么接下来让我们介绍一下解决嵌套组件通信这个问题的关键:react.children.map和react.cloneelement。
react.children
react.children是专门用来处理this.props.children这个东西的工具。
通常props.children可以是任何变量类型:数组、对象、文本或者其他的一些类型,但是我们这里使用
react.children.map(this.props.children,(child)=>{ // *** })
无论this.props.children的类型是什么都不会报错。
这里只是用了react.children的map函数,实际上它还有foreach,count以及only的玩法。
foreach就不解释了,很容易理解是干嘛的。
count就是得到被嵌套组件的数量。
only就是返回被嵌套的组件,并且只能有一个被嵌套的组件,否则会抛异常。
react.cloneelement
先看下面这段代码
const child= <child value={1} /> const newchild=react.cloneelement(child,{ name:'额外的props' },'123')
newchild的值为:
<child value={1} name='额外的props' > 123 </child>
可以很明显看到,react.cloneelement的就相当克隆一个组件,然后可以传给它额外的props和children。
总结
对于简单的嵌套组件用最开始的方法其实已经够了。
但是对于复杂的嵌套组件为了更好更方便的使用,往往需要与被嵌套的组件进行通信。
而我们可以使用react.children和react.cloneelement来解决这个问题。
上一篇: 老年人吃什么好?清淡,补充营养看过来
下一篇: 地五会穴的准确位置图及穴位功效