前言
在 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 来解决这个问题.
来源: https://www.cnblogs.com/vvjiang/p/9293741.html