Reac生命周期中组件创建阶段的过程详解
componentWillMount();
componentWillMount(): 组件将要挂载到页面时触发该函数,此时组件还没有挂载到页面上。而且内存中,虚拟的Dom结构还没有被创建出来,但是初始化时的默认属性和this.state的属性是可以在函数中访问到的,可以调用组件中定义的方法。类比Vue中生命周期的created()阶段。
注意点
ReactV16.3对生命周期的部分做了一些新的处理,为componentWillMount,componentWillReceiveProps,componentWillUpdate添加一个“UNSAFE_”前缀。 (这里,“不安全”不是指安全性,而是表示使用这些生命周期的代码将更有可能在未来的React版本中存在缺陷,特别是一旦启用了异步渲染)。
所以在16.3及以后的版本中用UNSAFE_componentWillMount来替代componentWillMount,不替换也可以但是在控制台会有警告。
详细内容:ReactV16.3即将更改的生命周期
render()
render():该阶段是在内存中创建虚拟Dom的阶段,整个过程结束后,内存中就已经创建完成了一个虚拟Dom,但是并未将其挂载到页面上,所以在这个阶段也是无法获取任何dom元素的。
componentDidMount()
组件已经挂载到了页面上后就进入了该阶段,在这个阶段中,页面已经存在可见的Dom元素了,在这个页面可以随心所欲的操作页面上的dom元素,在该阶段,在React中同样也可以用原生js来为元素绑定事件,尽管react中可以用原生js来为元素绑定事件,但是react中最好不要直接去操作dom元素,我们通常使用react提供的机制绑定事件。之后组件就进入了运行阶段。
React生命周期的创建阶段的各个回调函数的演示
组件CounterThree.jsx
import React from 'react'
//导入参数传递校验的模块
import dataRules from 'prop-types'
export default class CounterThree extends React.Component{
constructor(props){
super(props)
this.state = {
message:'hello',
//将props.oneCount赋值给state中的一个属性
count:props.oneCount
}
}
static defaultProps = {
oneCount:10
}
static propTypes = {
oneCount:dataRules.number
}
//componentWillMount 组件将要挂载到页面时触发该函数,此时组件还没有挂载到页面上
//而且内存中,虚拟的Dom结构还没有被创建出来,但是初始化时的默认属性和this.state的属性是可以在函数中访问到的,可以调用组件中定义的方法
UNSAFE_componentWillMount(){
console.log(document.getElementById('oneDom'));//null 此时虚拟dom还未创建
console.log(this.props.oneCount)//100
console.log(this.state.message)//hello
this.oneMethod();//我是oneMethod方法
}
render(){
console.log(document.getElementById('oneDom'))
console.log('-------------------')
//在return之前 虚拟Dom还未创建完成,页面是空的,拿不到任何元素
return <div>
<h3 id = "oneDom">这是一个Counter计数器组件</h3>
<button id="btn" onClick = {()=>this.changeData()}>点击+1</button>
<hr/>
<h3>当前的数量是{this.state.count}</h3>
</div>
//return 执行完毕之后,虚拟Dom结构已经创建完,但还没有挂载到页面上
}
componentDidMount(){
//该阶段类比Vue中生命周期的mounted
//如果我们想操作dom元素,最早可以在该阶段去操作
console.log(document.getElementById('oneDom'))
// document.getElementById('btn').onclick = ()=>{
// // console.log('原生js在react绑定的事件')
// this.setState({
// count:this.state.count+1
// })
// }
}
oneMethod(){
console.log('我是oneMethod方法')
}
changeData=()=>{
this.setState({
count:this.state.count+1
})
}
}
入口文件index.js
// console.log("React真好用")
import React from 'react'
import ReactDom from 'react-dom'
import CounterThree from '@/components/CounterThree'
ReactDom.render(<div>
{/* 在这里规定每个使用该组件的用户必须传递一个默认的数量值,作为组件初始化的数据 */}
<CounterThree oneCount = {100}></CounterThree>
</div>,document.getElementById('app'))
Reac生命周期中组件运行阶段的过程详解
shouldComponentUpdate(nextProps,nextState);
shouldComponentUpdate()判断组件是否需要更新 返回布尔值;
返回true则会调用render()重新渲染页面,之后数据和页面都是最新的;
如果返回false,不会执行后续的生命周期函数,render()函数也不会调用,将会继续返回组件的运行中的状态,数据得到更新,组件的state状态会被修改,但是页面并没有重新渲染,是旧的。
在该组件中,通过this.state.count拿到的属性值是旧的,并不是最新的,在这里可以通过nextProps和nextState两个参数去获取到对应的最新的属性值。
componentWillUpdate()
组件将要更新阶段的状态,在该状态下,内存中的虚拟dom和页面上的dom还都是旧的,所以在该阶段要谨慎操作dom,因为很可能只是操作的旧的Dom。
render()
运行阶段再一次执行render()函数。在组件运行阶段,componentWillUpdate()过后还会再次调用render()函数,在render()执行完毕之前,页面上的dom还是旧的。
componentDidUpdate()
组件完成了更新的状态,在该状态下,数据和内存中的虚拟dom以及页面上的dom都是最新的,此时可以放心大胆的去操作Dom。
组件CounterFour.jsx
import React from 'react'
//导入参数传递校验的模块
import dataRules from 'prop-types'
export default class CounterThree extends React.Component{
constructor(props){
super(props)
this.state = {
message:'hello',
count:props.oneCount
}
}
static defaultProps = {
oneCount:10
}
static propTypes = {
oneCount:dataRules.number
}
UNSAFE_componentWillMount(){
console.log(document.getElementById('oneDom'));//null 此时虚拟dom还未创建
console.log(this.props.oneCount)//100
console.log(this.state.message)//hello
this.oneMethod();//我是oneMethod方法
}
render(){
//在组件运行阶段,componentWillUpdate()过后还会再次调用render()函数,在render()执行完毕之前,页面上的dom还是旧的。
console.log(this.refs.h3 && this.refs.h3.innerHTML+'--------------运行阶段调用render()时')
return <div>
<h3 id = "oneDom">这是一个Counter计数器组件</h3>
<button id="btn" onClick = {()=>this.changeData()}>点击+1</button>
<hr/>
<h3 ref = 'h3'>当前的数量是{this.state.count}</h3>
</div>
}
componentDidMount(){
console.log(document.getElementById('oneDom'))
}
shouldComponentUpdate(nextProps,nextState){
//shouldComponentUpdate()判断组件是否需要更新 返回布尔值 返回true则会调用render()重新渲染页面,之后数据和页面都是最新的
//如果返回false,不会执行后续的生命周期函数,render()函数也不会调用,将会继续返回组件的运行中的状态,数据得到更新,组件的state状态会被修改,但是页面并没有重新渲染,是旧的。
//在该组件中,通过this.state.count拿到的属性值是旧的,并不是最新的,在这里可以通过nextProps和nextState去获取到对应的最新的属性值
// return this.state.count%2?false:true
return nextState.count%2?false:true
//只有偶数时才更新页面
}
//组件将要更新阶段的状态,在该状态下,内存中的虚拟dom和页面上的dom还都是旧的,所以在该阶段要谨慎操作dom,因为很可能只是操作的旧的Dom
componentWillUpdate(){
console.log(this.refs.h3.innerHTML+'---------------componentWillUpdate');
//打印出来的是旧dom的innerHTML
}
//组件完成了更新的状态,在该状态下,数据和内存中的虚拟dom以及页面上的dom都是最新的,此时可以放心大胆的去操作dom
componentDidUpdate(){
console.log(this.refs.h3.innerHTML+'---------------componentDidUpdate');
}
oneMethod(){
console.log('我是oneMethod方法')
}
changeData=()=>{
this.setState({
count:this.state.count+1
})
}
}
入口文件 index.js
// console.log("React真好用")
import React from 'react'
import ReactDom from 'react-dom'
import CounterFour from '@/components/CounterFour'
ReactDom.render(<div>
{/* 在这里规定每个使用该组件的用户必须传递一个默认的数量值,作为组件初始化的数据 */}
<CounterFour oneCount = {0}></CounterFour>
</div>,document.getElementById('app'))
运行阶段的componentWillReceiveProps()
该阶段,在组件将要接受外界传递过来的新的props属性值时触发,组件第一次被渲染到页面的时候是不会触发该状态的。只有通过某些事件,修改了props属性值之后,才会触发。
- 注意:在React16.3的版本中,componentWillReceiveProps更名为UNSAFE_componentWillReceiveProps在UNSAFE_componentWillReceiveProps被触发的时候,如果通过this.props来获取属性值,获得的是修改之前的属性值。如果想要获得最新的属性值,要通过其参数列表来获取。UNSAFE_componentWillReceiveProps(nextProps)
如下:
import React from 'react'
export default class CounterFive extends React.Component{
constructor(props){
super(props)
this.state = {
message:'我是子组件'
}
}
render(){
return <div>
<h3>我是父组件</h3>
<button onClick = {()=>this.changeData()}>点击改变数据</button>
<hr/>
<Son msg = {this.state.message}></Son>
</div>
}
changeData=()=>{
this.setState({
message:'哈哈哈,哈哈哈'
})
}
}
class Son extends React.Component{
constructor(props){
super(props)
this.state = {}
}
render(){
return <div>
<h5>{this.props.msg}</h5>
</div>
}
//第一次渲染时是不会触发该状态的,在传递的参数被修改后才会触发
UNSAFE_componentWillReceiveProps(nextProps){
//想要获得最新的属性值,要通过其参数列表来获取
console.log(this.props.msg+'------'+nextProps.msg)
//我是子组件 -------哈哈哈,哈哈哈
}
}
15、React中绑定this传参的三种方式
import React from 'react'
import DataTypes from 'prop-types'
export default class CounterSix extends React.Component{
constructor(props){
super(props)
this.state = {
message:'绑定this并传参的几种方式',
datamsg:'我是数据'
}
//绑定this并传参的方式二:在构造函数中绑定并传参
//当为一个函数绑定bind,改变this的指向后,bind函数调用的结果,有一个返回值,这个返回值是改变this指向的函数的引用
this.changedata2 = this.changedata2.bind(this,1000,2000)
}
render(){
return <div>
<h3>{this.state.message}</h3>
{/* bind的作用,为前面的函数,修改函数内部的this指向,让函数内部的this指向bind参数列表中的第一个参数。
bind和call和apply之间的区别
call和apply在修改完this的指向后会立即调用前面的函数
但是bind不会立即调用。bind参数列表中的第一个参数是用来修改this指向的,之后的参数,会被当做将来调用前面的函数的参数传递进去。 */}
<button onClick = {this.changedata.bind(this,100,200)}>绑定this并传参的方式一</button>
<hr/>
<button onClick = {this.changedata2}>绑定this并传参的方式二</button>
<hr/>
<button onClick = {()=>this.changedata3(200,500)}>绑定this并传参的方式三</button>
<hr/>
<p>{this.state.datamsg}</p>
</div>
}
//值的注意的是,因为上面绑定处理方法的时候,使用了bind,所以这里可以不再使用箭头函数。
changedata(num1,num2){
this.setState({
datamsg:'我被改变了'+num1+num2
})
}
changedata2(num1,num2){
this.setState({
datamsg:'我被改变了'+num1+num2
})
}
changedata3(num1,num2){
this.setState({
datamsg:'我被改变了'+num1+num2
})
}
}
16、一个评论列表的案例
-
采用组件化的方式,创建一个评论列表 如下
index.js如下:
import React from 'react'
import ReactDom from 'react-dom'
import PList from '@/components/PList'
ReactDom.render(<div>
<PList></PList>
</div>,document.getElementById('app'))
发表评论的组件 Sendpl.jsx
import React from 'react'
export default class Sendpl extends React.Component{
constructor(props){
super(props)
this.state = {}
}
render(){
return <div>
<label htmlFor="pinglunren">评论人</label><br/>
<input type="text" name = "pinglunren" ref = 'pinglunren'/><br/>
<label htmlFor="contentBox">评论内容</label><br/>
<textarea name="contentBox" id="contentBox" cols="30" rows="10" ref = "contentBox"></textarea>
<button onClick = {()=>this.Addpl()}>发表评论</button>
</div>
}
Addpl = ()=>{
//1、获取评论人和评论内容
//2、从本地存储中获取获取之前的评论数组
//3、把最新的评论人和评论内容放到数组中
//4、把最新的数组存储在本地并清空相关区域
const content = {name:this.refs.pinglunren.value,words:this.refs.contentBox.value}
const pllist = JSON.parse(localStorage.getItem('lists')||'[]')
pllist.unshift(content)
localStorage.setItem('lists',JSON.stringify(pllist))
console.log(localStorage.getItem('lists'))
this.refs.pinglunren.value=this.refs.contentBox.value=''
//调用传递的getPL方法刷新评论列表
this.props.reloadlist()
}
}
评论列表组件Plitem.jsx组件
import React from 'react'
export default class Plitem extends React.Component{
constructor(props){
super(props)
this.state = {}
}
render(){
return <div style = {{border:'1px solid #ccc',margin:'15px 0'}}>
<h3>评论人:{this.props.name}</h3>
<p>评论内容:{this.props.words}</p>
</div>
}
}
组件间的嵌套关系 Plist.jsx
import React from 'react'
import Plitem from '@/components/Plitem'
import Sendpl from '@/components/Sendpl'
export default class PList extends React.Component{
constructor(props){
super(props)
this.state = {
list:[
{id:0,name:'tom',words:'hello'},
{id:1,name:'jack',words:'world'},
{id:2,name:'cat',words:'byebye'}
]
}
}
render(){
return <div>
{/* 评论标题 */}
<h3>评论列表</h3>
{/* 发表评论组件 */}
{/* 在react中传递给组件数据或者方法都可以使用this.props.属性(或者方法名)来调用 ,这与Vue中数据传递使用props,方法传递使用this.$emit(方法名)是有不同的*/}
{/* 在该组件点击发表评论时应该再一次调用UNSAFE_componentWillMount中执行的方法getPL(),刷新评论列表 */}
<Sendpl reloadlist = {this.getPL}></Sendpl>
{/* 评论列表组件 */}
{this.state.list.map(item=>{
return <Plitem {...item} key={item.name}></Plitem>
})}
</div>
}
//获取评论数组
getPL=()=>{
var getpl = JSON.parse(localStorage.getItem('lists')||'[]')
this.setState({
list:getpl
})
}
//虚拟Dom挂载到页面之前调用getPL该方法,从本地取出数据替换掉之前的假数据
UNSAFE_componentWillMount(){
this.getPL()
}
}
效果如下
17、React中的context特性
- 当一个组件内的其他子组件需要使用父组件的数据时,为了避免不必要的繁琐,可以使用context特性
如下:
import React from 'react'
import ReactTypes from 'prop-types'
// export default class Father extends React.Component{
// constructor(props){
// super(props)
// this.state = {
// color:'red'
// }
// }
// render(){
// return <div>
// <h2>我是父组件</h2>
// <Son color = {this.state.color}></Son>
// </div>
// }
// }
// class Son extends React.Component{
// constructor(props){
// super(props)
// this.state = {}
// }
// render(){
// return <div>
// <h4>我是子组件</h4>
// <Grandson color = {this.props.color}></Grandson>
// </div>
// }
// }
// class Grandson extends React.Component{
// constructor(props){
// super(props)
// this.state = {}
// }
// render(){
// return <div style = {{color:this.props.color}}>
// <h5>我是孙子组件</h5>
// </div>
// }
// }
//以上案例的目的,是孙组件如果想用到父组件的state里的值,
//是经过了多次传导才得到的,而且子组件并没有使用该值,但是也参与了其中,这样子看上去过于繁琐,所以为了避免有时候出现这种状况,可以使用react中的Context的属性,如下:。
export default class Father extends React.Component{
constructor(props){
super(props)
this.state = {
color:'red'
}
}
// react中Context的使用
//1、在父组件中,创建一个function,它有个固定的名称,getChildContext,这个方法内部返回一个对象,将需要共享给其他子组件的数据包含在其中。
//2、需要使用属性校验,规定共享给子组件的数据的类型,childContextTypes
getChildContext(){
return {
color:this.state.color
}
}
static childContextTypes = {
color:ReactTypes.string
}
render(){
return <div>
<h2>我是父组件</h2>
<Son></Son>
</div>
}
}
class Son extends React.Component{
constructor(props){
super(props)
this.state = {}
}
render(){
return <div>
<h4>我是子组件</h4>
<Grandson></Grandson>
</div>
}
}
class Grandson extends React.Component{
constructor(props){
super(props)
this.state = {}
}
//使用父组件共享的数据时同样也是必须先进行校验
static contextTypes = {
color:ReactTypes.string
}
render(){
return <div>
<h5 style = {{color:this.context.color}}>我是孙子组件----{this.context.color}</h5>
</div>
}
}