点击这里进入react原理专栏react
看react
源码有一段时间了,写篇文章总结一下,也但愿能帮到你们。这是react原理系列的第一篇,主要讲解一下react
的基本架构,让你们有一个总体的认识。面试
fiber
,为何要使用fiber
react
的三层架构模型:scheduler - reconciler - renderer
react
合成事件,当咱们点击一个按钮触发click
事件时发生了什么react
是如何触发更新的(类组件的setState
,函数组件的useState
)react
更新的render
阶段react
更新的commit
阶段scheduler
调度任务的流程系列文章的react版本都为17.0.2算法
为了下降react
源码新手的困惑,咱们直接从数据结构上来理解什么是fiber
,每一个fiber
就是一个对象,每一个组件(好比App
组件),每一个真实的dom
节点都会对应一个fiber
对象,fiber
对象有不少属性,这里先介绍以下几个:数组
Fiber: {
return: Fiber | null,
child: Fiber | null,
sibling: Fiber | null,
stateNode: null
}
复制代码
return
属性指向父级fiber
,child
属性指向第一个子fiber
,sibling
执行本身的下一个兄弟fiber
节点,stateNode
执行这个fiber
对象对应的组件或者真实的dom
节点。浏览器
在采用fiber
架构以前,react
采用递归的方式处理虚拟dom,致使react
占用主线程的时间过长,可能形成页面假死。从前面的图能够看出,fiber
架构下的应用fiber
树是一个相似链表结构的多叉树,每一个fiber
是一个独立的工做单元,这就为可中断的更新提供了便利。考虑如下两个状况:缓存
react
更新过程当中,用户点击了按钮针对状况1,react
但愿可以在触发点击事件时,用户可以尽快获得响应,页面动画不会卡顿。在采用fiber
架构时,每一个fiber
都是一个独立的单元,react
可以在事件触发时中断fiber
的更新,转而处理用户点击事件,处理完毕后继续进行fiber
的更新。markdown
针对状况2,react
会以fiber
为基本单位进行更新,并为每一个fiber
的处理分配一个时间片,每次处理完一个fiber
后,会检查时间片是否到期,若是时间片到期,react
就会将线程让给浏览器,让浏览器执行相应的页面更新。数据结构
要想实现上面提到的两种效果,react
使用了fiber
架构,并引入了scheduler
模块和优先级的概念。下面介绍一下scheduler
架构
上面提到的两种状况,以后在
concurrent mode
下才会产生,使用ReactDOM.render
建立的应用是没有以上特性的,因此react17也被成为一个过渡版本。dom
react
采用了双缓存机制来保存fiber
树,即内存中存在两棵fiber
树,称为current
树和workInProgress
树。current
树表示当前正在页面中显示的fiber
节点,每次触发更新时,react
会根据current
树,并结合触发的更新,采用深度优先遍历的方式建立workInProgress
树。当workInProgress
建立完毕后,react
会切换两棵树,从而完成应用的更新。
在具体实现上,react
中有一个fiberRoot
节点,两个rootFiber
节点。fiberRoot
表示整个应用的根节点,每次触发更新时,都会从这个节点开始向下遍历。fiberRoot
有一个current
属性,指向current
树。每次应用更新结束后,切换current
指针的指向,就完成了页面的更新。
两个rootFiber
节点就是current
树和workInProgress
树的根节点,而且每一个current
树节点和对应的workInProgress
树节点都会有一个指针指向对方:alternate
属性。
react
对于workInProgress
的建立过程是很复杂的,这涉及到render
阶段的diff
流程,以后会单独讲解。
这里先简单介绍一下scheduler
这个模块,这个模块提供的功能就是提供任务调度功能,而且为任务提供优先级,实现高优先级任务打断低优先级任务。这样,react
就能在更新时及时响应用户触发的事件。
看下面的代码,你能说出输出结果吗?
class App extends React.Component {
componentDidMount() {
outer.addEventListener('click', function() {
console.log('native event click outer')
})
inner.addEventListener('click', function() {
console.log('native event click inner')
})
}
handleClickInner = () => {
console.log('react event click inner')
}
handleClickOuter = () => {
console.log('react event click outer')
}
render() {
return (
<div className='outer' onClick={this.handleClickInner}> <div className='inner' onClick={this.handleClickOuter}></div> </div>
)
}
}
复制代码
这里就涉及到了react
的合成事件机制,以后会有专门的文章进行介绍。
一个经典面试题了:setState
究竟是同步仍是异步的,好比下面这段代码的输出结果
// 异步
handleClick = () => {
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
}
// 同步
handleClick = () => {
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
})
}
复制代码
以后也会有专门的文章讲解react
是如何建立更新,如何处理更新的。
hooks
是react16一个重要的更新,hooks
让咱们可以在函数组件中使用state
,以后也会有一篇文章讲解hooks
是如何实现的。
react
的一次更新包括两个阶段:render
阶段和commit
阶段
render
阶段:包括了更新的计算,fiber
树的diff算法,effectList
的处理,dom节点的建立,类组件的生命周期等等
commit
阶段:包括类组件的生命周期,useEffect
的调度,setState
回调函数的执行,dom节点的插入等等
以后也会有专门的文章讲解这两个阶段的流程。
因为本身水平有限,并且react
源码内容繁多,结构复杂,再加上react17做为一个过渡版本,有不少为concurrent mode
作铺垫的代码,因此阅读react
源码是比较困难的,针对不少内容,笔者的理解也并不深入,所以暂时先不作介绍,将来有机会的话会再写文章进行介绍。
最后但愿你们在看完这个系列文章以后可以对react的总体运行流程有一个比较全面的认识。
推荐@Axizs大佬的react原理系列文章