React源码解析与优化

重要通知

研究与解析React源码的动机,是基于React源码的实现机制,更好地在基础架构与业务具体实现中将代码质量提升到更好的水平,同时能够兼顾更好状态的性能提升,可以充分运用React的核心优势,降低系统设计的冗余与业务实现的臃肿,实现以React生态技术栈为中心的最佳架构设计与业务功能实现。本文档的所有解析与优化,都是基于version: "18.2.0"版本。

基本概况

React与微线程

链表、事件循环

环状流的概念

页面模板渲染工作流程

页面模板代码示例

<div id="app"></div>

<script type="text/javascript">
  class App extends React.Component {
    constructor(props) {
      super(props);
      this.state = { title: "React源码解析与优化" };
    }

    render() {
      return (
        <section>{this.state.title}</section>
      );
    }
  };


  const htmlFragment = <App />;

  const dom = ReactDOM.createRoot(
    document.querySelector('#app'),
  ).render(htmlFragment);
</script>

Fiber数据结构

function FiberNode(tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode) {
  // Instance 实例属性
  this.tag = tag; // 标记不同组件类型,如函数组件、类组件、文本、原生组件...
  this.key = key; // react 元素上的 key 就是 jsx 上写的那个 key ,也就是最终 ReactElement 上的
  this.elementType = null; // createElement的第一个参数,ReactElement 上的 type
  this.type = null; // 表示fiber的真实类型 ,elementType 基本一样,在使用了懒加载之类的功能时可能会不一样
  this.stateNode = null; // 实例对象,比如 class 组件 new 完后就挂载在这个属性上面,如果是RootFiber,那么它上面挂的是 FiberRoot,如果是原生节点就是 dom 对象

  // fiber
  this.return = null; // 父节点,指向上一个 fiber
  this.child = null; // 子节点,指向自身下面的第一个 fiber
  this.sibling = null; // 兄弟组件, 指向一个兄弟节点
  this.index = 0; //  一般如果没有兄弟节点的话是0 当某个父节点下的子节点是数组类型的时候会给每个子节点一个 index,index 和 key 要一起做 diff
  this.ref = null; // reactElement 上的 ref 属性
  this.pendingProps = pendingProps; // 新的 props
  this.memoizedProps = null; // 旧的 props
  this.updateQueue = null; // fiber 上的更新队列执行一次 setState 就会往这个属性上挂一个新的更新, 每条更新最终会形成一个链表结构,最后做批量更新
  this.memoizedState = null; // 对应  memoizedProps,上次渲染的 state,相当于当前的 state,理解成 prev 和 next 的关系
  this.mode = mode; // 表示当前组件下的子组件的渲染方式

  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;
  this.lanes = NoLanes;
  this.childLanes = NoLanes;
  this.alternate = null; // current 树和 workInprogress 树之间的相互引用
}  

Fiber双缓存

在经过reconcile(diff)形成了新的workInProgress Fiber,然后将workInProgress Fiber切换成current Fiber应用到真实dom中,存在双Fiber的好处是在内存中形成视图的描述,在最后应用到dom中,减少了对dom的操作。

var createFiber = function (tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode) {
  return new FiberNode(tag, pendingProps, key, mode);
};
function createWorkInProgress(current: Fiber, pendingProps, expirationTime): Fiber {}
function resetWorkInProgress(workInProgress: Fiber, renderExpirationTime) {}

调度器-Scheduler

调度任务的优先级,高优任务优先进入Reconciler。

协调器-Reconciler

异步可中断更新,负责找出变化的组件,使用时间片

渲染器-Render

负责将变化的组件渲染到页面上。

ReactDOM

state更新机制

  • 触发更新的方式
    • ReactDOM.render()
    • this.setState
    • this.forceUpdate:设置update.tag为ForceUpdate,
    • useState
    • useReducer
Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
} 
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};  
  • state更新流程
    • React 组件状态更新,向 Scheduler 中存入一个任务,该任务为 React 更新算法。[Scheduler 需要暴露 pushTask() 方法,React 通过该方法存入任务。]
    • Scheduler 调度该任务,执行 React 更新算法。[Scheduler 需要暴露 scheduleTask() 方法,用于调度任务。]
    • React 在调和阶段更新一个 Fiber 之后,会询问 Scheduler 是否需要暂停。如果不需要暂停,则重复步骤 3,继续更新下一个 Fiber。[Scheduler 需要暴露 shouldYield() 方法,React 通过该方法决定是否需要暂停执行该任务。]
    • 如果 Scheduler 表示需要暂停,则 React 将返回一个函数,该函数用于告诉 Scheduler 任务还没有完成。Scheduler 将在未来某时刻调度该任务。[Scheduler 判断任务执行后的返回值是否是一个函数,如果是则说明任务未完成,将来还需要调度它。]
enqueueSetState(inst, payload, callback) {
  const fiber = getInstance(inst);
  const eventTime = requestEventTime();
  const lane = requestUpdateLane(fiber);

  const update = createUpdate(eventTime, lane);
  update.payload = payload;
  if (callback !== undefined && callback !== null) {
    if (__DEV__) {
      warnOnInvalidCallback(callback, 'setState');
    }
    update.callback = callback;
  }

  enqueueUpdate(fiber, update, lane);
  const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
  if (root !== null) {
    entangleTransitions(root, fiber, lane);
  }

  if (__DEV__) {
    if (enableDebugTracing) {
      if (fiber.mode & DebugTracingMode) {
        const name = getComponentNameFromFiber(fiber) || 'Unknown';
        logStateUpdateScheduled(name, lane, payload);
      }
    }
  }

  if (enableSchedulingProfiler) {
    markStateUpdateScheduled(fiber, lane);
  }
}  

模板字符串工作流程

响应式系统工作流程

Diff算法

JSX实现原理

JSX是React.createElement(component, props, ...children) 函数的语法糖。

<div className="sidebar" />

// Babel编译成
React.createElement(
  'div',
  {className: 'sidebar'},
  null
)

<div>Hello world!</div>
// Babel编译成
React.createElement(
  "div", 
  null, 
  "Hello world!"
)

Event事件系统

批量更新 batchedUpdates

Time Slicing 时间分片

把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。结合了requestAnimationFrame和MessageChannel实现了类似requestIdleCallback的功能,requestIdleCallback并不稳定,所以React抛弃了它。

任务优先级

React Fiber 把更新过程碎片化,每执行完一段更新过程,就把控制权交还给 React 负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务。

Redux源码解析

Recoil源码解析

Last Updated:
Contributors: 709992523, Eshen