参照: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组件的生命周期有关,组件挂载时有关的生命周期有以下几个:
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(
store ={store}>
, document.getElementById('root'))
为何可以这样作的原因,是Redux的store中的状态有一个最初始的值(reducer上传参里的默认值),组件先初始化完成,接著异步的fetch用promise语法,在作完外部数据加载后,发送动作出来,此时reducer更动store里的状态,react-redux绑定器会触发React组件的重渲染,所以组件上数据会自动更新。
有问题再问吧,代码写得简略但测试过是可执行的。
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);
}
function enqueueUpdate(component){
//injected注入的
ensureInjected();
//如果不处于批量更新模式
if(!batchingStrategy.isBatchingUpdates){
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
//如果处于批量更新模式
dirtyComponents.push(component);
}
setState方法通过一个队列机制实现state更新,当执行setState的时候,会将需要更新的state合并之后放入状态队列 ,批量更新,而不会立即更新this.state(可以和浏览器的事件队列类比)。
那么我们要如何解决上面的问题呢?
网上关于react 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