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来解决这个问题。

(0)

相关推荐

  • React组件重构之嵌套+继承及高阶组件详解

    前言 在最近做的一个react项目中,遇到了一个比较典型的需要重构的场景:提取两个组件中共同的部分. 最开始通过使用嵌套组件和继承的方式完成了这次重构. 但是后来又用高阶组件重新写了一遍,发现更好一点. 在这里记录下这两种方式以便之后参考和演进. 本次重构的场景 因为场景涉及到具体的业务,所以我现在将它简化为一个简单的场景. 现在有两个黑色箱子,箱子上都有一个红色按钮,A箱子充满气体,按了按钮之后箱子里面气体变红,B箱子充满泥土,按了之后箱子里面泥土变红. 那么现在上一个简单的重构前代码: Bo

  • 深入理解React中es6创建组件this的方法

    首发于:https://mingjiezhang.github.io/. 在JavaScript中,this对象是运行时基于函数的执行环境(也就是上下文)绑定的. 从react中的demo说起 Facebook最近一次更新react时,将es6中的class加入了组件的创建方式当中.Facebook也推荐组件创建使用通过定义一个继承自 React.Component 的class来定义一个组件类.官方的demo: class LikeButton extends React.Component

  • React如何利用相对于根目录进行引用组件详解

    前言 本文主要给大家介绍了关于React利用相对于根目录进行引用组件的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 在对自己开发的组件中经常会做诸如以下的引用: import genFetchEntryListArgs from '../../../utils/table/genFetchEntryListArgs'; import { parseQuery, stringifyQuery } from '../../../utils/query'; import

  • 使用React实现轮播效果组件示例代码

    前言 我发现React和AngularJS思想完全不同,AngularJS是基于双向绑定,在Modal层中定制数据,然后双向改变.但是React是通过prop和state来改变view层的状态.下面是我写的一个轮播图组件,可以直接看一下.代码很简单.原理就是通过React在componentDidMount后改变setState,来动态改变css样式. 说明以下:看gif很卡,但是实际效果还是很好的. 以下是示例代码 LunBo.js require('styles/App.css'); req

  • React组件的三种写法总结

    React 专注于 view 层,组件化则是 React 的基础,也是其核心理念之一,一个完整的应用将由一个个独立的组件拼装而成. 截至目前 React 已经更新到 v15.4.2,由于 ES6 的普及和不同业务场景的影响,我们会发现目前主要有三种创建 React 组件的写法:1. ES5写法React.createClass,2. ES6写法React.Component,3. 无状态的函数式写法(纯组件-SFC). 你们最钟爱哪种写法呢?萝卜青菜各有所爱~ 每个团队都有自己的代码规范和开发模

  • 详解react如何在组件中获取路由参数

    路由参数 假如我们有很多 list 页面,这些页面除了动态内容不同,其他的页面部分都相同,这个时候需要怎么配置路由和组件呢? 这种场景就需要用到路由的参数功能,增加一条包含参数的路由配置. import List from './component/list'; <Route path="list/:id" component={List} /> 注意 path 属性中的 :id 就是该路由的参数( param ).再来看看 List 页面的组件. /list 对应了 li

  • React 子组件向父组件传值的方法

    本文介绍了React 子组件向父组件传值的方法,分享给大家 子组件需要控制自己的 state, 然后告诉父组件自己的state,通过props调用父组件中用来控制state的函数,在父组件中展示子组件的state变化. /***实现在输入框输入邮箱时,在div中即时显示输入内容***/ <body> <div id="test"></div> </body> //子组件 var Child = React.createClass({ re

  • React创建组件的三种方式及其区别

    React推出后,出于不同的原因先后出现三种定义react组件的方式,殊途同归:具体的三种方式: 函数式定义的无状态组件 es5原生方式React.createClass定义的组件 es6形式的extends React.Component定义的组件 虽然有三种方式可以定义react的组件,那么这三种定义组件方式有什么不同呢?或者说为什么会出现对应的定义方式呢?下面就简单介绍一下. 无状态函数式组件 创建无状态函数式组件形式是从React 0.14版本开始出现的.它是为了创建纯展示组件,这种组件

  • react 父组件与子组件之间的值传递的方法

    概念上,组件是封闭的环境.React中是单向数据流的设计,也就是是说只有父组件传递资料给子组件这回事.以正确的技术说明,拥有者组件可以设置被拥有者组件中的数据. 那么子组件要如何与父组件沟通这件事,简单的来说,是一种迂回的作法,在父组件中设置了一个方法(函数),然后传递给子组件的props,子组件在事件触发时,直接呼叫这个props所设置的方法(函数).但这中间,有谁(对象)呼叫了函数的设置,也就是this的作用. 父组件到子组件用props设置,子组件到父组件用上面说的方式,这是基本的套路,但

  • React中的生命周期和子组件

    目录 组件生命周期 创建创建期 获取虚拟DOM 子组件 组件生命周期 为了说明组件的创建,存在,销毁的过程,react提供了组件的生命周期,共分三大周期: 创建期:说明组件的创建的过程,相当于人的少年 存在期:说明组件的存在的过程,相当于人的中年 销毁期:说明组件的销毁的过程,相当于人的老年 创建创建期 创建期共分五个阶段: ES5开发中,对应五个方法:getDefaultProps,getInitialsate, ​​componentWillMount​​, ​​render​​, ​​co

  • React Hook父组件如何获取子组件的数据/函数

    目录 React Hook父组件获取子组件数据/函数 子组件MyWorldMap 父组件MyTrip React Hook父组件提交子组件form 父组件 子组件 React Hook父组件获取子组件数据/函数 我们知道在react中,常用props实现子组件数据到父组件的传递,但是父组件调用子组件的功能却不常用. 文档上说ref其实不是最佳的选择,但是想着偷懒不学redux,在网上找了很多教程,要不就是hook的讲的太少,要不就是父子组件傻傻分不清,于是只好再啃了一下文档,就学了一下其它hoo

  • React中react-redux和路由详解

    目录 观察者模式解决组件间通信问题 react-redux connect方法 Provider组件 路由 使用路由 默认路由 路由重定向 获取路由参数 路由导航 观察者模式解决组件间通信问题 使用观察者解决组件间通信,分成两步 在一个组件中,订阅消息 在另一个组件中,发布消息 发布消息之后,订阅的消息回调函数会执行,在函数中,我们修改状态,这样就可以实现组件间通信了.这就是reflux框架的实现. react-redux redux早期被设计成可以在各个框架中使用,因此在不同的框架中使用的时候

  • React中嵌套组件与被嵌套组件的通信过程

    前言 在React项目的开发中经常会遇到这样一个场景:嵌套组件与被嵌套组件的通信. 比如Tab组件啊,或者下拉框组件. 场景 这里应用一个最简单的Tab组件来呈现这个场景. import React, { Component, PropTypes } from 'react' class Tab extends Component { static propTypes = { children: PropTypes.node } render() { return ( <ul> {this.p

  • vue中keep-alive组件实现多级嵌套路由的缓存

    目录 现状(问题): 探索方案: 实现方式 现状(问题): keep-alive 组件对第三级及以上级的路由页面缓存失效 探索方案: 方案1.直接将路由扁平化配置,都放在一级或二级路由中方案2.再一层缓存组件用来过渡,并将其name配置到include中 实现方式 方案1不需要例子,按规则配置路由就行重点介绍方案2因为我用了vue-element-admin做了架构,并且项目中我将菜单和路由全部通过服务端返回做了统一配置,所以我只能用方案2来实现. 直接看原有代码(问题代码) // src/la

  • 详解React中的组件通信问题

    引入 本来我是没想过总结这些东西的,会感觉比较入门.但是之前同学去腾讯面试问到了这个问题(react或vue的组件通信),我帮他整理,顺便写demo的过程中,会有一些新的体会,多总结还是有利于进步的呀. 父子组件 父 → 子 parent组件传给child组件,符合react的单向数据流理念,自上到下传递props. // 父组件 class Parent extends Component { constructor() { super(); this.state = { value: '',

  • 关于react中组件通信的几种方式详解

    前言 刚入门React可能会因为React的单向数据流的特性而遇到组件间沟通的麻烦,下面这篇文章就来给大家详细介绍下,在开始之前先来看一张图: react组件通信 需要组件之进行通信的几种情况 父组件向子组件通信 子组件向父组件通信 跨级组件通信 没有嵌套关系组件之间的通信 1. 父组件向子组件通信 React数据流动是单向的,父组件向子组件通信也是最常见的;父组件通过props向子组件传递需要的信息 Child.jsx import React from 'react'; import Pro

  • 浅谈React中组件逻辑复用的那些事儿

    基本每个开发者都需要考虑逻辑复用的问题,否则你的项目中将充斥着大量的重复代码.那么 React 是怎么复用组件逻辑的呢?本文将一一介绍 React 复用组件逻辑的几种方法,希望你读完之后能够有所收获.如果你对这些内容已经非常清楚,那么略过本文即可. 我已尽量对文中的代码和内容进行了校验,但是因为自身知识水平限制,难免有错误,欢迎在评论区指正. 1. Mixins Mixins 事实上是 React.createClass 的产物了.当然,如果你曾经在低版本的 react 中使用过 Mixins,

  • 详解React中共享组件逻辑的三种方式

    废话少说,这三种方式分别是:render props.高阶组件和自定义Hook.下面依次演示 假设有一个TimeOnPage组件专门用来记录用户在当前页面停留时间,像这样: const TimeOnPage = () => { const [second, setSecond] = useState(0); useEffect(() => { setTimeout(() => { setSecond(second + 1); }, 1000); }, [second]); return

随机推荐