内容简介:上回说到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爆了" ) 复制代码
还是老老实实地用 h1
、 div
这种标准的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.Component
的 constructor
方法。如果我们在子类 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中的元素变化是怎么变的呢?
先来一个官方的生命周期(我看着就头晕):
官方的全周期:
官方的简约版周期:
有没有看着头疼,反正我是跪了,真令人头大的生命周期啊。我还是通过实战来确认这个更新是怎么产生的吧。实战出真理!(一些不安全的方法,或者一些我们不太用得到的,这里就不讨论了。)
***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> ] ) } 复制代码
父元素成功获取来自子元素的慰问!
这次就科普到这里吧。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。