react生命周期详解

参照:https://www.jianshu.com/p/4784216b8194

测试例子:http://wximg.gtimg.com/shake_tv/test/lifeCycle2113.html

react生命周期函数:

1. constructor(props,context);
2. componentWillMount()挂载前调用,一次,如果此函数内调用setState,本次render函数可以看到更新后的state,并只渲染一次;
3. render()在componentWillMount后,componentDidMount前执行。触发条件:1.初始化页面,2.状态机改变setState,3.新props(父组件更新)。注意:组件所必不可少的核心函数;不能在该函数中修改状态机state;
4. componentDidMount()挂载后调用,一次,可使用refs属性;
5. componentWillReceiveProps(nextProps),props是父组件传给子组件的,父组件发生render时即调用;

6. shouldComponentUpdate(nextProps,nextState),

收到新的state或props时被调用。组件挂在后,每次setState后都会调用,判断是否需要重新渲染组件,默认返回true,需要重新render;if true,调用componentWillUpdate(nextProps,nextState);

7. componentWillUpdate()收到新的state或props后,立即调用。(被动的吧);
8. componentDidUpdate()重新渲染后调用,初始化时不会调用。作用:组件更新后,操作DOM元素;
9. componentWillUnmount()组件被卸载前调用。作用:清理,如清除定时器,或清除在componentDidMount()中创建的DOM元素。

问题一 React数据获取为什么一定要在componentDidMount里面调用?

这与React组件的生命周期有关,组件挂载时有关的生命周期有以下几个:

  • constructor()

  • componentWillMount()

  • render()

  • componentDidMount()

上面这些方法的调用是有次序的,由上而下,也就是当说如果你要获取外部数据并加载到组件上,只能在组件"已经"挂载到真实的网页上才能作这事情,其它情况你是加载不到组件的。

componentDidMount方法中的代码,是在组件已经完全挂载到网页上才会调用被执行,所以可以保证数据的加载。此外,在这方法中调用setState方法,会触发重渲染。所以,官方设计这个方法就是用来加载外部数据用的,或处理其他的副作用代码。

constructor被调用是在组件准备要挂载的最一开始,所以此时组件尚未挂载到网页上。

componentWillMount方法的调用在constructor之后,在render之前,在这方法里的代码调用setState方法不会触发重渲染,所以它一般不会用来作加载数据之用,它也很少被使用到。

一般的从后台(服务器)获取的数据,都会与组件上要用的数据加载有关,所以都在componentDidMount方法里面作。虽然与组件上的数据无关的加载,也可以在constructor里作,但constructor是作组件state初绐化工作,并不是设计来作加载数据这工作的,所以所有有副作用的代码都会集中在componentDidMount方法里。


补充一下,Redux作初始数据载入时,是可以不需透过React组件的生命周期方法,大致的方式如下代码:

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './App'

// reducer
function items(state = [], action) {
  switch (action.type) {
    case 'LOAD_ITEMS':
      return [...action.payload]
    default:
      return state
  }
}

// 创建store
const store = createStore(items)

fetch('http://localhost:8888/items', {
  method: 'GET'
})
.then((response) => {
  // ok代表状态码在200-299
  if (!response.ok) throw new Error(response.statusText)
  return response.json()
})
.then((itemList) => {
  // 作dispatch动作,载入外部数据完成之后
  store.dispatch({ type: 'LOAD_ITEMS', payload: itemList })
})
.catch((error) => { throw new Error(error.message) })

// React组件加载到真实DOM上
ReactDOM.render(
<Provider store={store}>
 <App />
</Provider>, document.getElementById('root'))

为何可以这样作的原因,是Redux的store中的状态有一个最初始的值(reducer上传参里的默认值),组件先初始化完成,接著异步的fetch用promise语法,在作完外部数据加载后,发送动作出来,此时reducer更动store里的状态,react-redux绑定器会触发React组件的重渲染,所以组件上数据会自动更新。

有问题再问吧,代码写得简略但测试过是可执行的。

问题二:setState

componentDidMount() {
    SynapseAnalytics.init({ type:Enum.pageTypeEnum.otherPage });
    this.setState({
        val:this.state.val + 1
    });
    //第一次输出  0
    console.log(this.state.val);
    this.setState({
        val:this.state.val + 1
    });
    //第二次输出 0
    console.log(this.state.val);
    setTimeout(()=>{
        this.setState({val:this.state.val + 1});
        //第三次输出 2
        console.log(this.state.val);
        this.setState({
            val:this.state.val + 1
        });
        //第四次输出 3
        console.log(this.state.val);
    }, 0);
} 
  • 依次输出0、0、2、3;因为react并不是setState之后state的值就会改变的,若是这样就太消耗内存了,失去了setState存在的意义。
  • 这里存在一个setstate调用栈的问题,问题来了setState之后都发生了什么
  • 1.this.setState(newState) =>
  • 2.newState存入pending队列 =>
  • 3.调用enqueueUpdate =>
  • 4.是否处于批量更新模式 => 是的话将组件保存到dirtyComponents
  • 5.不是的话遍历dirtyComponents,调用updateComponent,更新pending state or props
  • enqueueUpdate的源码如下:
  function enqueueUpdate(component){
       //injected注入的
       ensureInjected();
       //如果不处于批量更新模式
       if(!batchingStrategy.isBatchingUpdates){
           batchingStrategy.batchedUpdates(enqueueUpdate, component);
           return;
       }
       //如果处于批量更新模式
       dirtyComponents.push(component);
   } 
  • 如果isBatchingUpdates为true,则对所有队列中的更新执行batchedUpdates方法,否则只把当前组件(即调用了setState的组件)放入dirtyComponents数组中,例子中4次setState调用的表现之所以不同,这里的逻辑判断起了关键作用。

setState方法通过一个队列机制实现state更新,当执行setState的时候,会将需要更新的state合并之后放入状态队列 ,批量更新,而不会立即更新this.state(可以和浏览器的事件队列类比)。

那么我们要如何解决上面的问题呢?

网上关于react setState的结论不少,比如:

  • setState不会立刻改变React组件中state的值;
  • setState通过引发一次组件的更新过程来引发重新绘制
  • 多次setState函数调用产生的效果会合并。
  • 利用回调函数
  • 利用setTimeout,在setTimeout函数中,在this.setState之后this.state是立即更新的,所以也可以获取到更新后的数据。
  • 函数式的setState用法

setState从来不负责更新操作。它的工作只是把state,和callback放进序列,并且把要更新的组件放到dirtyComponents序列。

我们来看一下React文档中对setState的说明

 void setState(
    function|object nextState,
    [function callback]
 )

The second (optional) parameter is a callback function that will be executed once setState is completed and the component is re-rendered. 
翻译一下,第二个参数是一个回调函数,在setState的异步操作结束并且组件已经重新渲染的时候执行。也就是说,我们可以通过这个回调来拿到更新的state的值。 

//回调函数callback

this.setState({
  val: this.state.val+1
}, () => {
  console.log(this.state.val)
});
//将this.setState放入setTimeout函数中,
setTimeout中的setState是立即更新的
let self = this;
setTimeout(function () {
  self.setState({
    val:self.state.val+1
  });
  console.log(self.state.val);
})

如果传递给this.setState的参数不是一个对象而是一个函数,那游戏规则就变了。

 

这个函数会接收到两个参数,第一个是当前的state值,第二个是当前的props,这个函数应该返回一个对象,这个对象代表想要对this.state的更改,换句话说,之前你想给this.setState传递什么对象参数,在这种函数里就返回什么对象,不过,计算这个对象的方法有些改变,不再依赖于this.state,而是依赖于输入参数state。

比如,对于上面增加state上count的例子,可以这么写一个函数。

function increment(state, props) {
  return {count: state.count + 1};
}

可以看到,同样是把状态中的count加1,但是状态的来源不是this.state,而是输入参数state。

对应incrementMultiple的函数就是这么写。

function incrementMultiple() {
  this.setState(increment);
  this.setState(increment);
  this.setState(increment);
}

对于多次调用函数式setState的情况,React会保证调用每次increment时,state都已经合并了之前的状态修改结果。

简单说,加入当前this.state.count的值是0,第一次调用this.setState(increment),传给increment的state参数是0,第二调用时,state参数是1,第三次调用是,参数是2,最终incrementMultiple的效果,真的就是让this.state.count变成了3,这个函数incrementMultiple终于实至名归。

值得一提的是,在increment函数被调用时,this.state并没有被改变,依然,要等到render函数被重新执行时(或者shouldComponentUpdate函数返回false之后)才被改变。

让setState接受一个函数的API设计很棒!因为这符合函数式编程的思想,让开发者写出没有副作用的函数,我们的increment函数并不去修改组件状态,只是把“希望的状态改变”返回给React,维护状态这些苦力活完全交给React去做。

正因为流程的控制权交给了React,所以React才能协调多个setState调用的关系。

让我们再往前推进一步,试着如果把两种setState的用法混用,那会有什么效果?

我们把incrementMultiple改成这样。

function incrementMultiple() {
  this.setState(increment);
  this.setState(increment);
  this.setState({count: this.state.count + 1});
  this.setState(increment);
}

在几个函数式setState调用中插入一个传统式setState调用(嗯,我们姑且这么称呼以前的setState使用方式),最后得到的结果是让this.state.count增加了2,而不是增加4。

原因也很简单,因为React会依次合并所有setState产生的效果,虽然前两个函数式setState调用产生的效果是count加2,但是半路杀出一个传统式setState调用,一下子强行把积攒的效果清空,用count加1取代。

这么看来,传统式setState的存在,会把函数式setState拖下水啊!只要有一个传统式的setState调用,就把其他函数式setState调用给害了。

如果说setState这儿API将来如何改进,也许就该完全采用函数为参数的调用方法,废止对象为参数的调用方法。

 

参考 : https://segmentfault.com/a/119000001147452,https://zhuanlan.zhihu.com/p/25954470

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值