先来几个术语:

官方 我的说法 对应代码
React element React元素 let element=<span>A爆了</span>
Component 组件 class App extends React.Component {}
App为父元素,App1为子元素 <App><App1></App1></App>

本文重点:

  • 组件有两个特性

    • 1、传入了一个“props”
    • 2、返回了一个React元素
  • 组件的构造函数
    • 如果需要重新定义constructor,必须super一下,才能激活this,也就是可以用来自React.component方法
  • 组件的props
    • 是可读的,也就是不能在组件中修改prop的属性
    • JSX中传入对象的props,可以通过{...object}的方式
  • 父子元素之间的通信(初级版本)
    • 父=>子,通过父元素的render既可改变子元素的内容。
    • 子=>夫,通过父元素传入子元素中的props上挂载的方法,让子元素触发父元素中的方法,从而进行通信。

Component

上回说到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这个对象。

如果不想用箭头函数,那么就要注意了,我们可以在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版本

官方的全周期:

官方的简约版周期:

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

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");
}

好了开始看图推理

初始化运行状态:

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

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

同样的先父元素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>
)
}

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之间的通信方式了解下的更多相关文章

  1. React Hooks 你不来了解下?

    前言 最近在看 React 的新语法-- React Hooks,只能一句话概括:React 语法真的是越来越强大,越写代码越少. 强烈推荐还没看 React Hooks 的同学去学习下,这会让你写r ...

  2. vue组件之间的通信方式

    组件之间的通信方式有很多种 这里分享4种组件之间的通信方式 props(主要是父传子)  自定义事件(主要是子传父)  pubsub消息订阅与发布  xuex 1.props和自定义事件 app.vu ...

  3. [React] Styling React Components With Aphrodite

    Aphrodite is a library styling React components. You get all the benefits of inline styles (encapsul ...

  4. apple watch 与 iphone 之间的通信方式

    apple watch 与 iphone 之间的通信方式:通过watchkit WatchKit应用扩展都提供一个名为WKInterfaceController的子类来管理相应的界面. 启动watch ...

  5. Rendering React components to the document body

    React一个比较好用的功能是其简单的API,一个组件可以简单到一个return了组件结构的render函数.除了一个简单的函数之外,我们还有了一段有用且可复用的代码片段. 问题 不过有时候可能会受到 ...

  6. React Components Template

    React Components Template "use strict"; /** * * @author xgqfrms * @license MIT * @copyrigh ...

  7. [React] Create and import React components with Markdown using MDXC

    In this lesson I demonstrate how to use the library MDXC to create and import React components with ...

  8. [Poi] Use Markdown as React Components by Adding a Webpack Loader to Poi

    Poi ships with many webpack loaders included, but you may run into scenarios where you'll need to cu ...

  9. [React] Recompose: Theme React Components Live with Context

    SASS Bootstrap allows us to configure theme or branding variables that affect all components (e.g. P ...

随机推荐

  1. hdu5475(线段树单点修改,统计区间乘积)

    题目意思: 给定a*b*c*d*e*f*....,可以在某一步去掉前面的一个因子,每次回答乘积. #include <cstdio> #include <cstring> #i ...

  2. Linux 系统管理命令 - vmstat - 虚拟内存统计

    命令详解 重要星级: ★★★★☆ 功能说明: vmstat 是 Virtual Memory Statistics ( 虚拟内存统计 ) 的缩写,利用 vmstat 命令可以对操作系统的内存信息.进程 ...

  3. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(四)图书信息的增删改查

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  4. E20180207-ts

    crumb n. 碎屑(尤指面包屑或糕饼屑); 面包心; 些许,少许; <俚>可鄙的人物;

  5. springMvc中实现拦截器Interceptor以及添加静态资源映射

    这个代码写了很久了,多久呢?2018年12-20号写的.... 废话不多说,简化一下,作为笔记. 注: public class springmvcConfig extends WebMvcConfi ...

  6. vscode等编辑器正则一键剔除注释的方法

    匹配HTML单行注释: <!--(.*?)--> 匹配HTML多行注释: <!--([\s\S|\r]*?)--> 或者: <!--([\w\W]*?)--> 除了 ...

  7. 标准行cp功能

    #include<stdio.h> int main(int argc,char *argv[]) { FILE *src_fp,*des_fp; ]={}; ) { printf(&qu ...

  8. phpstudy 集成的mysql 无法启动

    问题产生: 安装好phpstudy后,Apache可以启动,Mysql无法启动.  解决方法: 之前已经装过Mysql,要把系统服务里面的MySQL删除,留下MySQLa服务. 在cmd命令行下输入: ...

  9. [UOJ311]积劳成疾

    题解 dp 似乎这个最大值不好设计状态啊== 但是可以发现这\(n\)个点每个点都是相同的 可以设计状态\(f_{i,j}\)表示一个长度为\(i\)的一段区间的最大值不会超过\(j\)的价值 那么转 ...

  10. _bzoj1010 [HNOI2008]玩具装箱toy【斜率优化dp】

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1010 裸的斜率优化,第一次写队首维护的时候犯了个智障错误,队首维护就是维护队首,我怎么会那队 ...