先说几个名词:官方版本我的声明对应代码ReactelementReactelementletelement=A爆Component组件类AppextendsReact.Component{}没有App是父元素,App1是子元素本文重点:组件有两个特点1、一个“props”2传入,返回一个React元素如果组件的构造器需要重新定义构造器,必须super激活this,即从React.component方法组件中读取props即可,即组件中不能修改prop属性传入对象,在JSX中props可以通过{...object}(初级版本)parent=>child在父子元素之间进行通信,子元素的内容可以是通过父元素的渲染改变。Child=>Husband,将父元素传递给子元素中props上挂载的方法,让子元素触发父元素中的方法,从而进行通信。Component上次讲了JSX的用法,这次讲React组件之间的通信。那么什么是组件呢?我知道英语是Component,但对我来说只是一个词,毫无意义。要了解组件如何友好地通信,您必须首先了解什么是组件。上次提到的JSX,我们可以这样创建对象:letelement=Aburst//相当于letelement=React.createElement("h1",{className:"aaa"},"Aexploded")或者老老实实的使用标准的HTML标签元素比如h1和div来生成React元素。但是这样一来,我们的JS就会变得异常庞大,都是新创建的React元素,到时候可能连对象名怎么获取都不知道,说不定就变成了letdiv1;让div2。哈哈哈开玩笑的。但分离必须分离。这时候就有了一个概念,叫做Component。他能做什么?简单的说,就是创建独立的、可复用的widgets。话不多说,来看看官方的写法:写法一:用函数创建组件,大家可以看到,我直接定义了一个方法,名字叫App,每次执行App()都会返回一个新的方法.反应元素。而这个方法我们可以称之为组件Component。一些已经上手React的朋友可能会比较笨。这是什么操作?那我的高大上课呢?延长呢?不好意思告诉你,这也是一个组件,因为它符合官方的定义:1.传入一个“props”,2.返回一个React元素。满足以上两个条件就是Component!函数App(props){return{props.name}!Aexploded}这是最简单的组件。在我看来,Component本身就是对React.createElement的一种封装,它的render方法相当于React.createElement的功能。tall组件函数来了:importReact,{Component}from'react';classAppextendsComponent{render(){return{this.props.name}!一个爆炸的}}exportdefaultApp;这个类版本的组件和上面的纯方法组件,从React的角度来看,没有区别,但是!毕竟我的类方法也继承了React.Component,没有更多的小功能就没有意义了吧?所以我们认为继承React.Component的组件的initial函数比单纯的方法return要多。所以我们可以直接将每个ReactComponent作为一个React元素来使用。好吧,让我们研究一下Component类的方法。首先是一个神奇的构造函数,它在类中,可以说是一个初始化的函数。如果省略,则不会报错,因为我们的组件都是React.Component的子类,所以都继承了React.Component的构造方法。如果我们在子类Component中定义构造函数,就相当于重写了父类的方法,那么React.Component的构造函数就会失效。简单地说,许多默认分配是无效的。你不能得到道具。所以为了提醒大家不要忘记super,也就是继承父类的构造函数,会报错“thishasn'tbeeninitialized-super()hasn'tbeencalled”。这意味着你先继承。也就是说super是执行父类构造函数的方法。所以!!!重点来了——写super的时候不能忘记传入props。如果不传入props,程序将无法获取定义的组件属性。constructor(props){super(props);//等同于React.Component.call(this,props)}官方也给了大家一个重点:Class组件应该总是调用带有props的baseconstructor。(类组件应该总是用props调用基础构造函数,constructor必须和props一起使用。)我们没有写constructor,但是在其他内置方法,比如render中,props也可以直接获取,而这个奇怪的操作可以解释。因为我们省略了重定义,但是构造函数本身不仅存在而且执行,只是没有体现在我们写的子类中。分析完props的坑里的Component,有没有发现Component的一个局限性?这是正确的!是参考!Component的一个定义是只能传入props参数,也就是说所有的通信都必须在这个props中进行。有一种参观监狱的既视感。只能在指定窗口用对讲机聊天,不能用其他方式交流。React对props有严格的规定。详见前端进阶面试题答案所有React组件在props方面都必须表现得像纯函数。简单来说,props是不可更改的,是只读的。(不信邪的一定要试试,直接改props的值就可以了,最后肯定是等着报错页面。)这里需要科普一下纯函数的概念,后面Redux会遇到。这意味着纯函数只是一个过程,在此过程中没有任何对象的值发生变化。因为JS对象有一个很奇怪的现象。如果将对象传递给此方法并更改其属性之一的值,则传入的对象也会在函数外更改。纯函数意味着您的更改不会影响函数范围之外的对象。所以每次在Component中遇到一个新的对象状态,一般都会通过state改变当前组件中这个组件的数据。重点:由于JS的特性,为了不污染全局作用域,props设置为只读。这很大程度上保证了Component的独立性。相当于一个Component就是一个小世界。我发现定义props的值也是一门学问,还挺容易踩坑的。比如下面的代码,我觉得应该打印成props:{firstName:"Nana",lastName:"Sun"...},结果是props:{globalData:true}.letglobalData={firstName:"Nana",lastName:"Sun",greeting:["早安","下午好","晚安"]}ReactDOM.render(,document.getElementById('root'));那么如何将props传入组件,我觉得很有必要去研究一下。props其实就是直接传给组件的参数,没有做任何特殊处理。所以props的处理是在React.createElement这一步。让我们回顾一下React.createElement是如何工作的。React.createElement("yourTagName",{className:"aaa",color:"red:},"text/childnode")//对应的JSX写法为:text/Childnode即它的语法是一个属性名=属性值。如果我们直接放一个,它会被解析成},所以当然props一定不是我们想要的结果。这是他的一种语法,我们不能颠倒,但是我们可以改变写法,这样就不能解析成属性名=属性值,这种写法就是{...globalData},解构然后重建,仅此而已。Components之间的消息传递单个组件的Update->setStateComponents之间的消息传递是一个交互的过程,也就是说Component是“动态的”而不是“静态的”。所以首先,我们要“移动”静态Component,也就是更新组件的值。不是说道具是不能改的吗,那怎么改呢?前面说过,Component是一个小世界,所以这个世界有一个状态,叫做state。首先考虑如何通过外力改变Component的状态,比如点击、滑动。classAppextendsComponent{state={num:0}addNum=()=>{this.setState({num:this.state.num+1})}render(){return([{this.state.nu??m}
,点我+1])}}这里我使用了onClick用户的主动操作来强制更新组件。其实component的小世界主要是通过state来更新的,但是value不会直接通过this.state.XXX=xxx来改变,而是通过this.setState({...})来改变。这是一个小提示。我认为每个人都容易犯错误。关于箭头函数的this指向问题,请看下图。如果把箭头函数转成ES5,我们可以清楚的看到箭头函数指向的是上一层的函数对象。这也指向App对象。如果你不想使用箭头功能,那你就要注意了,我们可以在onClick里面加一个bind(this)来绑定this的指向,像这样onClick={this.addNum.bind(this)}。render(){return([{this.state.num}
,点我+1])}componentInter-通信,那么Component可以通过this.setState自高,那么组件之间呢?一个Component自己关闭不与其他Component协作是不可能的吧?那我们可以试试办法。在App中,我把{this.state.num}
抽取出来放在App1中,然后App1直接使用props来展示,因为props来自于父元素。相当于在App(父元素)中直接将num传递给App1(子元素)。每次App中状态发生变化,App1都会收到调用一起update。那么这个召唤是基于什么理论呢?这个时候就介绍一下React的生命周期lifecycle这个问题。//Apprender(){return([,clickme+1])}//App1render(){return([{this.props.num}
,])}react的生命周期看到lifecycle的生命周期,感觉是无穷无尽的循环!我要承认我在这个圈子里吗?React中的生命周期是怎样的?如果只是单纯的渲染,是没有生命周期的。毕竟,只要渲染了内容,任务就完成了。所以这里的生命周期肯定是和变化相关的。只有当有变化的时候,才需要重新渲染,然后改变,再渲染。这是一个循环,这就是生命周期。那么元素在React中是如何变化的呢?先来一个官方的生命周期(看得我头晕):点我看直播版官方全周期:官方精简版周期:看的头疼吗,反正我是跪着,真是头疼啊。让我通过实战来确认一下这个更新是如何产生的。实战揭示真相!(一些不安全的方法,或者一些我们用的不多的方法,这里就不讨论了)挂载设备阶段:constructor()render()componentDidMount()更新更新阶段:render()componentDidUpdate()有争议的componentWillReceiveProps()卸载阶段:componentWillUnmount()ErrorHandlingerrorcaptureextremecomponentDidCatch()这里我们通过运行代码来确认生命周期,这里是一段父元素嵌套子元素的代码,只是告诉大家,我在每个阶段打印发生了什么。这部分的例子,我还是用上面的App和App1的例子。//fatherconstructor(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");}//childconstructor(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");}好了~开始看图推理~初始化运行状态:parentelement先运行创建没有问题,但是问题是父元素还没有运行完,又杀了一个子元素。也就是说,当父元素在渲染过程中遇到子元素时,先加载子元素。子元素加载完成后,告诉父元素我已经加载了,父元素继续加载,直到结束。我点击了,父元素setState,然后更新了子元素的props。第一个父元素的相同渲染在遇到子元素时会暂时挂起。这时候子元素中出现了componentWillReceiveProps,也就是说他先知道父元素传递了props,然后render。因为有时候我们需要在拿到父元素改变的props后进行某种操作,所以componentWillReceiveProps就很有用了,否则会直接渲染子元素。突然想到,那么如果我的子元素中没有props,componentWillReceiveProps是不是不会被执行呢??也就是说,变成了。我还是天真。这个componentWillReceiveProps仍然会被执行。也就是说:componentWillReceiveProps并不是父元素传入的props发生了变化,而是父元素渲染了,子元素的这个方法才会被触发。关于卸载,我们来玩一下,把App的方法改成下面这样。当num等于2时,App1将不会显示。render(){return({this.state.num===2?"":
}
点我+1 )}App先渲染,然后卸载App1,然后完成componentDidUpdate更新。那么你了解生命周期吗??我总结为:加载父元素时,渲染子元素,先加载子元素,再继续加载父元素。父元素渲染时,子元素会触发componentWillReceiveProps,而当父元素跟随render卸载子元素时,先渲染,再卸载子元素,最后componentDidUpdate如何传递给父元素??通过生命周期,子元素可以很容易的获取父元素的内容,但是父元素如何从子元素中获取内容呢?我们不要忘记他们的沟通桥梁道具!我们可以在父元素中创建一个获取子元素信息的方法,然后绑定到子元素上,就可以获取了!操作如下所示:receiveFormChild=(value)=>{console.log(value)}render(){return({this.state.num===2?"":
}
点我+1 )}子元素运行popToFather时,消息可以送给爸爸!子元素:render(){return([{this.props.num}
,this.props.receiveState("Condolencesfromchildelement")}>传递从儿子到父亲])}父元素成功获得子元素的慰问!这次就到此为止吧。