React Components之间的通信方式了解下

栏目: 服务器 · 发布时间: 6年前

内容简介:上回说到JSX的用法,这回要开讲react组件之间的一个沟通。那么什么是组件?我知道英文是Component,但这对我而言就是一个单词,毫无意义。要了解Component之间是如何进行友好交流的,那就要先了解Component是个什么鬼。上回说到的JSX,我们可以这么创建对象:还是老老实实地用

上回说到JSX的用法,这回要开讲react组件之间的一个沟通。那么什么是组件?我知道英文是Component,但这对我而言就是一个单词,毫无意义。要了解Component之间是如何进行友好交流的,那就要先了解Component是个什么鬼。

上回说到的JSX,我们可以这么创建对象:

let element=<h1 className="aaa">A爆了</h1>
//等同于
let element=React.createElement(
  "h1",
  {className:"aaa"},
  "A爆了"
)
复制代码

还是老老实实地用 h1div 这种标准的HTML标签元素去生成React元素。但是这样的话,我们的JS就会变得巨大无比,全部都是新建的React元素,有可能到时候我们连对象名都不晓得怎么起了,也许就变成 let div1;let div2 这样的。哈哈哈开个玩笑。但是分离是肯定要分离的。这个时候就有了名为Component的概念。他可以做些什么呢?简单的说就是创建一个个 独立的可复用 的小组件。话不多说,我们来瞅瞅来自官方的写法:

写法一:函数型创建组件,大家可以看到我就直接定义一个名为App的方法,每次执行 App() 的时候就会返回一个新的React元素。而这个方法我们可以称之为组件Component。有些已经上手React的朋友,可能傻了了,这是什么操作,我的高大上 class 呢? extend 呢?很遗憾地告诉你,这也是组件,因为他符合官方定义:1、传入了一个“props” ,2、返回了一个React元素。满足上述两个条件就是Component!

function App(props) {
  return <span>{props.name}!A爆了</span>     
}
复制代码

这个是最简易的 Component 了,在我看来 Component 本身是对 React.createElement 的一种封装,他的 render 方法就相当于 React.createElement 的功能。高大上的组件功能来啦:

import React, { Component } from 'react';
class App extends Component {
  render() {
    return <span>{this.props.name}!A爆了</span>     
  }
}
export default App;
复制代码

这个 class 版本的组件和上方纯方法的组件,从React的角度上来说,并无不同,但是!毕竟我 class 的方式还继承了 React.Component ,不多点小功能都说不过去对吧?所以说我们这么想继承了 React.Component 的组件的初始功能要比纯方法return的要多。所以每个React的 Component 我们都可以当作React元素直接使用。

好了,我们来研究研究 Component 这个类的方法吧。

首先是一个神奇的 constructor 函数,这个函数在类中,可以说是用于初始化的函数。如果省去不写,也不会出错,因为我们的组件都是 React.Component 的子类,所以都继承了 React.Componentconstructor 方法。如果我们在子类 Component 中定义了 constructor 相当于是覆盖了父类的方法,这样 React.Component 的构造函数就失效了。简单地来说就是很多默认的赋值都失效了。你是获取不到 props 的。因此官方为了提醒大家不要忘记 super 一下,也就是继承父类的 constructor ,因此会报 "this hasn't been initialised - super() hasn't been called" 这个错误。意思就是你先继承一下。也就是说 super 是执行了父类的 constructor 的方法。所以!!!重点来了——我们写super的时候不能忘记传入 props 。不传入 props ,程序就无法获取定义的组件属性了。

constructor(props) {
    super(props);//相当于React.Component.call(this,props)
}
复制代码

官方也给大家划重点了:

Class components should always call the base constructor with props.(类组建在执行基本constructor的时候,必须和props一起。)

对于我们没有写 constructor ,但在其他自带方法中,比如 render ,也可以直接获取到 props ,这个诡异的操作就可以解释了。因为我们省略了重定义,但是 constructor 本身不仅是存在的而且也执行了,只不过没有在我们写的子类中体现出来而已。

props的坑

分析了Component之后,大家有没有发现Component的一个局限?没错!就是传参!关于Component的一个定义就是,只能传入 props 的参数。也就是说所有的沟通都要在这个 props 中进行。有种探监的既视感,只能在规定的窗口,拿着对讲机聊天,其他的方式无法沟通。React对于 props 有着苛刻的规定。

All React components must act like pure functions with respect to their props.

简单地来说就是 props 是不能被改变的,是只读的。(大家如果不信邪,要试试,可以直接改props的值,最终等待你的一定是报错页面。)

这里需要科普下 纯函数pure function 的概念,之后Redux也会遇到的。意思就是纯函数只是一个过程,期间不改变任何对象的值。因为JS的对象有个很奇怪的现象。如果你传入一个对象到这个方法中,并且改变了他某属性的值,那么传入的这个对象在函数外也会改变。 pure function 就是你的改动不能对函数作用域外的对象产生影响。所以每次我们在Component里面会遇到一个新的对象 state ,一般这个组件的数据我们会通过 state 在当前组件中进行变化处理。

划重点:因为JS的特性,所以 props 设置为只读,是为了不污染全局的作用域。这样很大程度上保证了 Component 的独立性。相当于一个 Component 就是一个小世界。

我发现定义props的值也是一门学问,也挺容易踩坑的。

比如下方代码,我认为打印出来应该是 props:{firstName:"Nana",lastName:"Sun"...} ,结果是 props:{globalData:true} .

let globalData={
    firstName:"Nana",
    lastName:"Sun",
    greeting:["Good moring","Good afternoon","Good night"]
}
ReactDOM.render(<App globalData/>, document.getElementById('root'));
复制代码

所以对于 props 是如何传入组件的,我觉得有必要研究一下下。

props 其实就是一个参数直接传入组件之中的,并未做什么特殊处理。所以对 props 进行处理的是在 React.createElement 这一个步骤之中。我们来回顾下 React.createElement 是怎么操作的。

React.createElement(
  "yourTagName",
  {className:"aaa",color:"red:},
  "文字/子节点"
)
//对应的JSX写法是:
<yourTagName className="aaa" color="red>文字/子节点</yourTagName>
复制代码

也就是他的语法是一个 属性名=属性值 ,如果我们直接放一个 <App globalData/> ,那么就会被解析成 <App globalData=true/>} ,所以props当然得不到我们想要的结果。这个是他的一个语法,我们无法扭转,但是我们可以换一种写法,让他无法解析成 属性名=属性值 ,这个写法就是 {...globalData} ,解构然后重构,这样就可以啦。

Components之间的消息传递

单个组件的更新->setState

Components之间的消息传递是一个互动的过程,也就是说Component是“动态”的而不是“静态”的。所以首先我们得让静态的 Component “动起来”,也就是更新组件的的值,前面不是提过 props 不能改嘛,那怎么改?前文提过 Component 就是一个小世界,所以这个世界有一个状态叫做 state

先考虑如何外力改变 Component 的状态,就比如点击啦,划过啦。

class App extends Component {
  state={
      num:0
  }
  addNum=()=>{
    this.setState({
      num:this.state.num+1
    })
  }
  render() {
    return( [
        <p>{this.state.num}</p>,
        <button onClick={this.addNum}>点我+1</button>
      ]
    )     
  }
}
复制代码

这里我用了 onClick 的用户主动操作的方式,迫使组件更新了。其实component这个小世界主要就是靠 state 来更新,但是不会直接 this.state.XXX=xxx 直接改变值,而是通过 this.setState({...}) 来改变。

这里有一个小tips,我感觉大家很容易犯错的地方,有关箭头函数的this指向问题,大家看下图。箭头函数转化成ES5的话,我们就可以很清晰得看到,箭头函数指向他上一层的函数对象。这里也就指向 App 这个对象。

React Components之间的通信方式了解下

如果不想用箭头函数,那么就要注意了,我们可以在onClick中加一个 bind(this) 来绑定this的指向,就像这样 onClick={this.addNum.bind(this)}

render() {
    return( [
        <p>{this.state.num}</p>,
        <button onClick={this.addNum.bind(this)}>点我+1</button>
      ]
    )     
  }
复制代码

组件之间的通信

那么Component通过 this.setState 可以自high了,那么组件之间的呢?Component不可能封闭自己,不和其他的Component合作啊?那我们可以尝试一种方式。

在App中我把 <p>{this.state.num}</p> 提取出来,放到App1中,然后App1直接用 props 来显示,因为props是来自父元素的。相当于我直接在App(父元素)中传递num给了App1(子元素)。每次App中state发生变化,那么App1就接收到召唤从而一起更新。那么这个召唤是基于一个什么样的理论呢?这个时候我就要引入React的生命周期life cycle的问题了。

//App
render() {
  return( [
      <App1 num={this.state.num}/>,
      <button onClick={this.addNum}>点我+1</button>
    ]
  )     
}
//App1
render() {
  return( [
      <p>{this.props.num}</p>,
    ]
  )     
}
复制代码

react的生命周期

看到生命周期life cycle,我就感觉到了生生不息的循环cycle啊!我是要交代在这个圈圈里了吗?react中的生命周期是干嘛的呢?如果只是单纯的渲染就没有生命周期一说了吧,毕竟只要把内容渲染出来,任务就完成了。所以这里的生命周期一定和变化有关,有变化才需要重新渲染,然后再变化,再渲染,这才是一个圈嘛,这才是life cycle。那么React中的元素变化是怎么变的呢?

先来一个官方的生命周期(我看着就头晕):

点我看live版本

官方的全周期:

React Components之间的通信方式了解下

官方的简约版周期:

React Components之间的通信方式了解下

有没有看着头疼,反正我是跪了,真令人头大的生命周期啊。我还是通过实战来确认这个更新是怎么产生的吧。实战出真理!(一些不安全的方法,或者一些我们不太用得到的,这里就不讨论了。)

***Mounting***装备阶段:

  • constructor()
  • render()
  • componentDidMount()

***Updating***更新阶段:

  • render()
  • componentDidUpdate()
  • 具有争议的componentWillReceiveProps()

***Unmounting***卸载阶段:

  • componentWillUnmount()

***Error Handling***错误捕获极端

  • componentDidCatch()

这里我们通过运行代码来确认生命周期,这里是一个父元素嵌套子元素的部分代码,就是告诉大家,我在每个阶段打印了啥。这部分的例子我用的还是上方的App和App1的例子。

//father
constructor(props){
  console.log("father-constructor");
}
componentDidMount() {
  console.log("father-componentDidMount");
}
componentWillUnmount() {
  console.log("father-componentWillUnmount");
}
componentDidUpdate() {
  console.log("father-componentDidUpdate");
}
render() {
  console.log("father-render");
}
复制代码
//child
constructor(props){
  console.log("child-constructor");
  super(props)
}
componentDidMount() {
  console.log("child-componentDidMount");
}
componentWillUnmount() {
  console.log("child-componentWillUnmount");
}
componentDidUpdate() {
  console.log("child-componentDidUpdate");
}
componentWillReceiveProps(){
  console.log("child-componentWillReceiveProps");
}
render() {
  console.log("child-render");
}
复制代码

好了~开始看图推理~

初始化运行状态:

React Components之间的通信方式了解下

父元素先运行创建这没有什么问题,但是问题是父元素还没有运行结束,杀出了一个子元素。也就是说父元素在render的时候里面碰到了子元素,就先装载子元素,等子元素装载完成后,再告诉父元素我装载完毕,父元素再继续装载直至结束。

我点击了一下,父元素 setState ,然后更新了子元素的 props

React Components之间的通信方式了解下

同样的先父元素render,遇到子元素就先暂时挂起。子元素这个时候出现了 componentWillReceiveProps ,也就是说他是先知道了父元素传 props 过来了,然后再 render 。因为有时候我们需要在获取到父元素改变的props之后再执行某种操作,所以 componentWillReceiveProps 很有用,不然子元素就直接 render 了。突想皮一下,那么我子元素里面没有props那是不是就不会执行 componentWillReceiveProps 了??就是 <App1 num={this.state.num}/> 变成 <App1/> 。我还是太天真了。这个 componentWillReceiveProps 依然会执行也就是说:

componentWillReceiveProps并不是父元素传入的 props 发生了改变,而是父元素 render 了,就会出发子元素的这个方法。

关于卸载,我们来玩一下,把App的方法改成如下方所示,当num等于2的时候,不显示App1。

render() {
  return( 
    <div>
      {this.state.num===2?"":<App1 num={this.state.num}/>}
      <button onClick={this.addNum}>点我+1</button>
    </div>
  )     
}
复制代码
React Components之间的通信方式了解下

App先 render ,然后卸载了App1之后,完成了更新 componentDidUpdate

那么大家看懂了生命周期了吗??我总结了下:

  • 父元素装载时 render 了子元素,就先装载子元素,再继续装载父元素。
  • 父元素 render 的时候,子元素就会触发 componentWillReceiveProps ,并且跟着 render
  • 父元素卸载子元素时,先 render ,然后卸载了子元素,最后 componentDidUpdate

如何子传父亲呢??

通过生命周期,子元素可以很容易的获取到父元素的内容,但是父元素如何获得来自子元素的内容呢?我们不要忘记了他们为一个沟通桥梁 props !我们可以在父元素中创建一个方法用于获取子元素的信息,然后绑定到子元素上,然后不就可以获取到了!操作如下所示:

receiveFormChild=(value)=>{
  console.log(value)
}
render() {
  return( 
    <div>
      {this.state.num===2?"":<App1 num={this.state.num} popToFather={this.receiveFormChild}/>}
      <button onClick={this.addNum}>点我+1</button>
    </div>
  )     
}
复制代码

当子元素运行 popToFather 的时候,消息就可以传给父亲啦!

子元素:

render() {
  return( [
      <p>{this.props.num}</p>,
      <button onClick={()=>this.props.receiveState("来自子元素的慰问")}>子传父</button>
    ]
  )     
}
复制代码
React Components之间的通信方式了解下

父元素成功获取来自子元素的慰问!

这次就科普到这里吧。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JavaScript Patterns

JavaScript Patterns

Stoyan Stefanov / O'Reilly Media, Inc. / 2010-09-21 / USD 29.99

What's the best approach for developing an application with JavaScript? This book helps you answer that question with numerous JavaScript coding patterns and best practices. If you're an experienced d......一起来看看 《JavaScript Patterns》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具