前言
在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来解决这个问题。
我是新来的。我试图用它来建立自己的网站。我想做的是在父组件中嵌套一个子组件;父组件应该在主页中呈现,子组件嵌套在主页中。下面我举了一个我尝试的例子。 到目前为止,我的方法是 在“childComponent”中创建子组件。js' 这里的问题是“childComponent”在我的React应用程序中永远不可见。我是不是走错了路?有没有我不懂的基本机械原理? 提前感谢!! -- 子组件(childC
嗨,我是ReactJS平台的学生开发者。我以前在render方法中使用过类组件,但现在我学习了钩子和函数组件对它的重要性,就像每个Reactjs开发人员所知道的那样。我在使用嵌套组件时遇到问题,我面临如下错误: index.js:1警告:函数作为React子函数无效。如果返回组件而不是从渲染返回组件,可能会发生这种情况。或者你想调用这个函数而不是返回它 你能帮我解决这个问题吗?如何在返回部分有效地
问题内容: 我对React有一点问题。我无法使用for循环创建嵌套组件。我想做的是创建一个表的9个单元格,然后创建3行,每行3个单元格,然后将3行安装在一起并创建9x9的电路板。 假设我想得到类似的东西,但是使用循环 } 我搜索了几个小时的其他问题,我认为我的代码几乎是正确的,但没有呈现我想要的内容。我只会得到一张白纸。 这是我的代码: 我总是在屏幕上看到这样的东西: 我想澄清一下,我确定该组件(
问题内容: 我有一个嵌套的数组,我想在其中显示结果的子集。例如,在下面的数组中,我想遍历嵌套array [1]中的所有值。 我试图使用foreach函数,但似乎无法正常工作。这是我的原始语法(尽管我意识到这是错误的)。 我试图避免对键是否与我要搜索的键相同的变量进行比较,即 有任何想法吗? 问题答案: 如果您知道嵌套数组中的级别数,则可以简单地执行嵌套循环。像这样: 如果您不知道数组的深度,则需要
问题内容: 所以,我有多个ReactComponent。最初,我以为会有一个带有其自身状态的父组件(简称为GrandPa),它将有关其状态的一些信息传递给另一个组件(称为Parent)。同样,父母将他的一些财产传给孩子,孩子则传给孙子。因此,我们具有层次结构: 爷爷->父母->孩子->孙子 但是,我很快意识到,当我使用method 更改GrandPa的状态时,所做的更改不会沿通道向下级联。我意识到
问题内容: 我的React组件有问题。我组件的嵌套子代似乎没有渲染。这是我的代码: 我在此文件的顶部有以下两行: 这是我的组件: 我努力了: 以完整的HTML标记调用组件(打开和关闭) 将组件嵌套在组件中(在index.jsx文件中) 我知道嵌套组件是可能的。我已经看到了。由于某种原因,它对我不起作用。 问题答案: 要允许组件包含子项并正确渲染它们,必须使用。这是传递到所有组件与孩子为道具,包含组