原文地址在个人博客, 转载请注明出处,谢谢!
随着项目开发的深刻,不可避免了遇到了一些问题。刚开始出现问题时很懵,不知道该怎么解决,缘由就是对React的原理理解的不够透彻,不知道问题出在哪。在解决问题的过程当中,也逐渐深刻了解了React的一些原理,这篇文章就来分享一下我对React一些原理的理解。javascript
注意
这篇文章并非教程,只是我对React原理的一些我的理解,欢迎与我一块儿讨论。文章不对的地方,还请读者费心指出^-^
本文是《使用React技术栈的一些收获》系列文章的第二篇(第一篇在这里,介绍如何开始构建React大型项目),简单介绍了React一些原理,包括React合成事件系统、组件的生命周期以及setState()
。html
React快速的缘由之一就是React不多直接操做DOM,浏览器事件也是同样。缘由是太多的浏览器事件会占用很大内存。java
React为此本身实现了一套合成系统,在DOM事件体系基础上作了很大改进,减小了内存消耗,简化了事件逻辑,最大化解决浏览器兼容问题。浏览器
其基本原理就是,全部在JSX声明的事件都会被委托在顶层document节点上,并根据事件名和组件名存储回调函数(listenerBank
)。每次当某个组件触发事件时,在document节点上绑定的监听函数(dispatchEvent
)就会找到这个组件和它的全部父组件(ancestors
),对每一个组件建立对应React合成事件(SyntheticEvent
)并批处理(runEventQueueInBatch(events)
),从而根据事件名和组件名调用(invokeGuardedCallback
)回调函数。框架
所以,若是你采用下面这种写法,而且这样的P标签有不少个:dom
listView = list.map((item,index) => { return ( <p onClick={this.handleClick} key={item.id}>{item.text}</p> ) })
That's OK,React帮你实现了事件委托
。我以前由于不了解React合成事件系统,还显示的使用了事件委托,如今看来是画蛇添足的。函数
因为React合成事件系统模拟事件冒泡的方法是构建一个本身及父组件队列,所以也带来一个问题,合成事件不能阻止原生事件,原生事件能够阻止合成事件。若是须要阻止事件传播, 仅用 event.stopPropagation() 是不行的, 由于React合成事件一样实现了stopPropagation(), 调用event.stopPropagation() 实际上调用了React的stopPropagation()(这里的event指的是React合成事件), 只能阻止React合成事件的传播, 要想完全阻止传播(包括原生事件), 须要调用React合成事件暴露的原声事件接口, 所以, 阻止事件传播须要同时调用合成事件与原生事件的接口:工具
stopPropagation: function(e){ e.stopPropagation(); e.nativeEvent.stopImmediatePropagation(); },
若是你想详细了解React合成事件系统,移步http://blog.csdn.net/u0135108...学习
为了搞清楚组件生命周期,构造一个父组件包含子组件而且重写各生命周期函数的场景:this
class Child extends React.Component { constructor() { super() console.log('Child was created!') } componentWillMount(){ console.log('Child componentWillMount!') } componentDidMount(){ console.log('Child componentDidMount!') } componentWillReceiveProps(nextProps){ console.log('Child componentWillReceiveProps:'+nextProps.data ) } shouldComponentUpdate(nextProps, nextState){ console.log('Child shouldComponentUpdate:'+ nextProps.data) return true } componentWillUpdate(nextProps, nextState){ console.log('Child componentWillUpdate:'+ nextProps.data) } componentDidUpdate(){ console.log('Child componentDidUpdate') } render() { console.log('render Child!') return ( <h1>Child recieve props: {this.props.data}</h1> ); } } class Father extends React.Component { // ... 前面跟子组件同样 handleChangeState(){ this.setState({randomData: Math.floor(Math.random()*50)}) } render() { console.log('render Father!') return ( <div> <Child data={this.state.randomData} /> <h1>Father State: { this.state.randomData}</h1> <button onClick={this.handleChangeState}>切换状态</button> </div> ); } } React.render( <Father />, document.getElementById('root') );
结果以下:
刚开始
调用父组件的setState后:
有一张图能说明这之间的流程(图片来源):
有一个能反映问题的场景:
... state = { count: 0 } componentDidMount() { this.setState({count: this.state.count + 1}) this.setState({count: this.state.count + 1}) this.setState({count: this.state.count + 1}) } ...
看起来state.count被增长了三次,但结果是增长了一次。这并不奇怪:
React快的缘由之一就是,在执行this.setState()
时,React没有忙着当即更新state
,只是把新的state
存到一个队列(batchUpdate
)中。上面三次执行setState
只是对传进去的对象进行了合并,而后再统一处理(批处理),触发从新渲染过程,所以只从新渲染一次,结果只增长了一次。这样作是很是明智的,由于在一个函数里调用多个setState是常见的,若是每一次调用setState都要引起从新渲染,显然不是最佳实践。React官方文档里也说了:
Think ofsetState()
as a request rather than an immediate command to update the component.把
setState()
看做是从新render的一次请求而不是马上更新组件的指令。
那么调用this.setState()
后何时this.state才会更新?
答案是即将要执行下一次的render
函数时。
这之间发生了什么?setState
调用后,React会执行一个事务(Transaction),在这个事务中,React将新state放进一个队列中,当事务完成后,React就会刷新队列,而后启动另外一个事务,这个事务包括执行 shouldComponentUpdate
方法来判断是否从新渲染,若是是,React就会进行state合并(state merge
),生成新的state和props;若是不是,React仍然会更新this.state
,只不过不会再render
了。
开发人员对setState
感到奇怪的缘由可能就是按照上述写法并不能产生预期效果,但幸运的是咱们改动一下就能够实现上述累加效果:
这归功于setState
能够接受函数做为参数:
setState(updater, [callback])
... state = { score: 0 } componentDidMount() { this.setState( (prevState) => ({score : prevState.score + 1}) ) this.setState( (prevState) => ({score : prevState.score + 1}) ) this.setState( (prevState) => ({score : prevState.score + 1}) ) } }
这个updater
能够为函数,该函数接受该组件前一刻的 state 以及当前的 props 做为参数,计算和返回下一刻的 state。
你会发现达到增长三次的目的了: 在Jsbin上试试看
这是由于React会把setState
里传进去的函数放在一个任务队列里,React 会依次调用队列中的函数,传递给它们前一刻的 state。
另外,不知道你在jsbin上的代码上注意到没有,调用setState
后console.log(this.state.score)
输出仍然为0,也就是this.state
并未改变,而且只render
了一次。
学习一个框架或者工具,我以为应该了解如下几点:
经过对React一些原理的简单了解,就懂得了React为何这么快速的缘由之一,也会在问题出现时知道错在什么地方,知道合理的解决方案。