react源码阅读:总体认识

点击这里进入react原理专栏react

react源码有一段时间了,写篇文章总结一下,也但愿能帮到你们。这是react原理系列的第一篇,主要讲解一下react的基本架构,让你们有一个总体的认识。面试

  1. 什么是fiber,为何要使用fiber
  2. react的三层架构模型:scheduler - reconciler - renderer
  3. react合成事件,当咱们点击一个按钮触发click事件时发生了什么
  4. react是如何触发更新的(类组件的setState,函数组件的useState)
  5. react更新的render阶段
  6. react更新的commit阶段
  7. scheduler调度任务的流程

系列文章的react版本都为17.0.2算法

什么是fiber

为了下降react源码新手的困惑,咱们直接从数据结构上来理解什么是fiber,每一个fiber就是一个对象,每一个组件(好比App组件),每一个真实的dom节点都会对应一个fiber对象,fiber对象有不少属性,这里先介绍以下几个:数组

Fiber: {
  return: Fiber | null,
  child: Fiber | null,
  sibling: Fiber | null,
  stateNode: null
}
复制代码

return属性指向父级fiberchild属性指向第一个子fibersibling执行本身的下一个兄弟fiber节点,stateNode执行这个fiber对象对应的组件或者真实的dom节点。浏览器

fiber.jpg

为何要用fiber

在采用fiber架构以前,react采用递归的方式处理虚拟dom,致使react占用主线程的时间过长,可能形成页面假死。从前面的图能够看出,fiber架构下的应用fiber树是一个相似链表结构的多叉树,每一个fiber是一个独立的工做单元,这就为可中断的更新提供了便利。考虑如下两个状况:缓存

  1. react更新过程当中,用户点击了按钮
  2. 页面动画

针对状况1,react但愿可以在触发点击事件时,用户可以尽快获得响应,页面动画不会卡顿。在采用fiber架构时,每一个fiber都是一个独立的单元,react可以在事件触发时中断fiber的更新,转而处理用户点击事件,处理完毕后继续进行fiber的更新。markdown

针对状况2,react会以fiber为基本单位进行更新,并为每一个fiber的处理分配一个时间片,每次处理完一个fiber后,会检查时间片是否到期,若是时间片到期,react就会将线程让给浏览器,让浏览器执行相应的页面更新。数据结构

要想实现上面提到的两种效果,react使用了fiber架构,并引入了scheduler模块和优先级的概念。下面介绍一下scheduler架构

上面提到的两种状况,以后在concurrent mode下才会产生,使用ReactDOM.render建立的应用是没有以上特性的,因此react17也被成为一个过渡版本。dom

fiber的工做流程

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属性。

双缓存.jpg

react对于workInProgress的建立过程是很复杂的,这涉及到render阶段的diff流程,以后会单独讲解。

scheduler

这里先简单介绍一下scheduler这个模块,这个模块提供的功能就是提供任务调度功能,而且为任务提供优先级,实现高优先级任务打断低优先级任务。这样,react就能在更新时及时响应用户触发的事件。

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是同步仍是异步

一个经典面试题了: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原理

hooks是react16一个重要的更新,hooks让咱们可以在函数组件中使用state,以后也会有一篇文章讲解hooks是如何实现的。

react更新的两大阶段

react的一次更新包括两个阶段:render阶段和commit阶段

render阶段:包括了更新的计算,fiber树的diff算法,effectList的处理,dom节点的建立,类组件的生命周期等等

commit阶段:包括类组件的生命周期,useEffect的调度,setState回调函数的执行,dom节点的插入等等

以后也会有专门的文章讲解这两个阶段的流程。

因为本身水平有限,并且react源码内容繁多,结构复杂,再加上react17做为一个过渡版本,有不少为concurrent mode作铺垫的代码,因此阅读react源码是比较困难的,针对不少内容,笔者的理解也并不深入,所以暂时先不作介绍,将来有机会的话会再写文章进行介绍。

最后但愿你们在看完这个系列文章以后可以对react的总体运行流程有一个比较全面的认识。

推荐@Axizs大佬的react原理系列文章

相关文章
相关标签/搜索