先来几个术语:
官网 | 我的说法 | 对应代码 |
---|---|---|
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 爆了"
)
还是老老实实地用 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 中的元素变动是怎么变的呢?
先来一个官网的生命周期(我看着就头晕):
点我看 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>
]
)
}
父元素胜利获取来自子元素的慰问!
这次就科普到这里吧。