react源码

220 阅读7分钟

基于react17

react17新增哪些内容?

react17基本概念

解决问题:react变慢

**办法:**使用异步可中断

实现

fiber:一套数据结构既能对应真实dom又能作为分隔单元,这就是Fiber

scheduler: 调度器。异步执行机制,js实现一套时间片运行的机制,类似requestIdleCallback

lane:   细粒度的管理各个任务的优先级,优先级高的优先执行。各个Fiber工作单元比较优先级,相同优先级的任务可以一起更新

上层实现

batchedUpdates 批量异步更新   Suspense

副作用

会对当前的渲染造成二次影响的操作。文件读取、接口请求等

从render开始看react架构

const state = reconcile(update); //render(update);
const UI = commit(state); //渲染层
  1. mount/update  jsx
  2. render阶段
  • Scheduler(调度器): 排序优先级,让优先级高的任务先进行reconcile
  • Reconciler 调节器 (协调层): 为每一个react元素创建 Fiber 数据结构并为 Fiber 节点打标记flag/effectTag属性,存储当前 Fiber 节点要进行的 DOM 操作 ,然后在render 结束后, fiber 会被保存到 fiberRoot 中
  1. commit阶段
  • Renderer(渲染层):先获取到render 的结果, 根据 fiber 中的 effectTag 属性进行相应的 DOM 操作,此阶段是不可以被打断的

jsx

React.createElement(返回virtual-dom对象)的语法糖

fiber双缓存

fiber中存储dom树、updateQueue、updte。其中dom树包含:virtual element、next、sibling、return(父节点)

双缓存是指内存中有两棵fiber树。一棵current fiber当前页面的fiber树,一棵是workInProgress fiber,在update的时候会diff current fiber生成workInProgress fiber,包含updateQueue、副作用有关的信息,updateQueue是单链表由多个update组成。在render阶段结束后作为current fiber渲染到页面。

两棵树之间使用alternate指针互相指向。可以确保diff、reconciler等阶段在内存中完成不阻塞页面渲染

scheduler

调度器,timeQueue taskQueue 异步可中断的触发任务。将高优先级的发送给conciler

lane模型

使用二进制最小细粒度的标记优先级  其中1出现次数越多优先级越高

reconciler 协调器

协调器,根据update,使用diff算法以 current fiber为模板打出flags(增删改) 和 effectFlags得出workInProgress Fiber

render阶段遍历Fiber树类似dfs的过程,‘捕获’阶段发生在beginWork函数中,该函数做的主要工作是创建Fiber节点计算statediff算法,‘冒泡’阶段发生在completeWork中,该函数主要是做一些收尾工作,例如处理节点的props、和形成一条effectList链表,该链表是被标记了更新的节点形成的链表(摒弃递归是为了做中断)

注意: fiberRoot是整个项目的根节点,只存在一个,rootFiber是应用的根节点,可能存在多个,例如多个ReactDOM.render(<App />, document.getElementById("root"));创建多个应用节点

rederer 渲染

commit操作前面的effectList(firstEffect、nextEffect)

commit主要操作在commitRoot函数中,遍历effectList,分别使用以下三个函数处理节点。commitBeforeMutationEffects(执行dom操作前: getSnapShotBeforeUpdte)、commitMutationEffects(执行dom操作)、commitLayoutEffects(执行dom操作后:componentDidMount、useEffect、componentDidUpdate),他们主要做的事情如下,后面会详细讲解,现在在大脑里有一个结构就行

concurrent  mode  异步并发

它是一类功能的合集(如fiber、schduler、lane、suspense),其目的是为了提高应用的响应速度,使应用cpu密集型的更新不在那么卡顿,其核心是实现了一套异步可中断、带优先级的更新。

给js操作一个时间片,如果超出时间仍然没有执行完毕,进入callbackQueue。暂停该js的执行,等下一帧继续执行,把执行权交回给浏览器去绘制。

fiber数据结构

fiberNode

//dom节点信息 tag key 

//树连接  child return sibling

//数据信息 prop state

//lane优先级  调度器调度以及reconciler触发顺序

//effectList  需要做的处理 firstEffect nextEffect lastEffect

//alternate  双缓存机制

//ReactFiber.old.js
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  //作为静态的数据结构 保存节点的信息 
  this.tag = tag;//对应组件的类型
  this.key = key;//key属性
  this.elementType = null;//元素类型
  this.type = null;//func或者class
  this.stateNode = null;//真实dom节点

  //作为fiber数架构 连接成fiber树
  this.return = null;//指向父节点
  this.child = null;//指向child
  this.sibling = null;//指向兄弟节点
  this.index = 0;

  this.ref = null;

  //用作为工作单元 来计算state
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;
    
	//effect相关
  this.effectTag = NoEffect;
  this.nextEffect = null;
  this.firstEffect = null;
  this.lastEffect = null;

  //优先级相关的属性
  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  //current和workInProgress的指针
  this.alternate = null;
}

 一次update触发流程

update&updateQueue

scheduler

render

commmit

jsx揭秘

 jsx是react.createElement的语法糖

在mount/update阶段,jsx会被babel转换成react.createElement(type/FuncCom/ClassCom,{...props},[...children]);createElement生成vNode即virtual dom

setState 创建update

export function createUpdate(eventTime: number, lane: Lane): Update<*> {//创建update
  const update: Update<*> = {
    eventTime,
    lane, //此update的优先级

    tag: UpdateState, //更新的类型,例如UpdateState、ReplaceState
    payload: null, //第一个参数  {data:12}
    callback: null, //第二个参数

    next: null, //链表,例如同时触发多个setState时会形成多个Update,用next连接
  };
  return update;
}//将update加入到updateQueue 一个链表中
//ReactUpdateQueue.old.js
export function initializeUpdateQueue<State>(fiber: Fiber): void {
  const queue: UpdateQueue<State> = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
      pending: null,
    },
    effects: null,
  };
  fiber.updateQueue = queue;
}


// 处理updateque中的update,优先级低的跳过,放入fiber.lanes中等待下轮处理
// effect记录中间产生的副作用
//-------------------------
// 进入schedule流程

react的diff算法

实现传统diff O(n^3)到O(n)的跃迁

diff策略

1.跨域层级的改动很少可以忽略不计。所以只比较同层级dom树的变动

2.同type的节点复用

3.同层级的一组子节点通过唯一的key值区分,移动位置复用

placeChild函数

//ReactChildFiber.old.js

function placeChild(newFiber, lastPlacedIndex, newIndex) {
       newFiber.index = newIndex;
   
       if (!shouldTrackSideEffects) {
         return lastPlacedIndex;
       }
   
    var current = newFiber.alternate;
 
       if (current !== null) {
         var oldIndex = current.index;
   
         if (oldIndex < lastPlacedIndex) {
           //oldIndex小于lastPlacedIndex的位置 则将节点插入到最后
           newFiber.flags = Placement;
           return lastPlacedIndex;
         } else {
           return oldIndex;//不需要移动 lastPlacedIndex = oldIndex;
         }
       } else {
         //新增插入
         newFiber.flags = Placement;
         return lastPlacedIndex;
       }
     }



function reconcilerChildernArray(
    returnFiber:Fiber,
    currentFirstChild:Fiber | null,
    newChildren:Arrry<*>,//用于比较的jsx数组
    lanes:Lanes
){

}

多节点集合情况

function reconcileChildrenArray(
    returnFiber: Fiber,//父fiber节点
    currentFirstChild: Fiber | null,//childs中第一个节点
    newChildren: Array<*>,//新节点数组 也就是jsx数组
    lanes: Lanes,//lane相关 第12章介绍
  ): Fiber | null {

    let resultingFirstChild: Fiber | null = null;//diff之后返回的第一个节点
    let previousNewFiber: Fiber | null = null;//新节点中上次对比过的节点

    let oldFiber = currentFirstChild;//正在对比的oldFiber
    let lastPlacedIndex = 0;//上次可复用的节点位置 或者oldFiber的位置
    let newIdx = 0;//新节点中对比到了的位置
    let nextOldFiber = null;//正在对比的oldFiber
    for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {//第一次遍历
      if (oldFiber.index > newIdx) {//nextOldFiber赋值
        nextOldFiber = oldFiber;
        oldFiber = null;
      } else {
        nextOldFiber = oldFiber.sibling;
      }
      const newFiber = updateSlot(
        //单节点比较 key、tag&type key不同则标记删除当前节点 type不同则标记删除当前节点和后面的children
        //更新节点,如果key不同则 return newFiber=null跳出本次diff,另外处理
        returnFiber,
        oldFiber,
        newChildren[newIdx],
        lanes,
      );
      if (newFiber === null) {
        if (oldFiber === null) {
          oldFiber = nextOldFiber;
        }
        break;//当curfiber或newfiber为null时结束树diff,单独处理剩余部分
      }
      if (shouldTrackSideEffects) {
        //检查shouldTrackSideEffects,检查是否是mount阶段
        if (oldFiber && newFiber.alternate === null) {
          deleteChild(returnFiber, oldFiber);//如果是mount将oldFiber删除
        }
      }
      //上面已经确认可以复用,此处placeIndx为当前的index
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);//标记节点插入
      //新节点链起来:sibling
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
      //老节点循环
      oldFiber = nextOldFiber;
    }

    //新节点已经遍历完成
    if (newIdx === newChildren.length) {
      deleteRemainingChildren(returnFiber, oldFiber);
      //将oldFiber中没遍历完的节点标记为DELETION
      return resultingFirstChild;
    }

    //老树已遍历结束则剩余新树都是插入操作
    if (oldFiber === null) {
      //处理新树剩余部分
      for (; newIdx < newChildren.length; newIdx++) {//第2次遍历
        const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
        if (newFiber === null) {
          continue;
        }
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);//插入新增节点
        if (previousNewFiber === null) {
          resultingFirstChild = newFiber;
        } else {
          previousNewFiber.sibling = newFiber;
        }
        previousNewFiber = newFiber;
      }
      return resultingFirstChild;
    }

    //oldFiber已经被多次 oldFiber=oldfiber.siblin 将剩下的oldFiber加入map中
    const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
    // 
    for (; newIdx < newChildren.length; newIdx++) {//第三次循环 处理节点移动
      const newFiber = updateFromMap(//查找符合的节点即:key不同但type相同的index
        existingChildren,
        returnFiber,
        newIdx,
        newChildren[newIdx],
        lanes,
      );
      if (newFiber !== null) {
        if (shouldTrackSideEffects) {
          if (newFiber.alternate !== null) {
            existingChildren.delete(//删除找到的节点
              newFiber.key === null ? newIdx : newFiber.key,
            );
          }
        }
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);//标记为插入的逻辑
        if (previousNewFiber === null) {
          resultingFirstChild = newFiber;
        } else {
          previousNewFiber.sibling = newFiber;
        }
        previousNewFiber = newFiber;
      }
    }

    if (shouldTrackSideEffects) {
      //删除existingChildren中剩下的节点
      existingChildren.forEach(child => deleteChild(returnFiber, child));
    }

    return resultingFirstChild;
  }

useState hooks手写

import React from "react";
import ReactDOM from "react-dom";

let workInProgressHook;//当前工作中的hook
let isMount = true;//是否时mount时

const fiber = {//fiber节点
  memoizedState: null,//hook链表
  stateNode: App//dom
};

const Dispatcher = (() => {//Dispatcher对象
  function mountWorkInProgressHook() {//mount时调用
    const hook = {//构建hook
      queue: {//更新队列
        pending: null//未执行的update队列
      },
      memoizedState: null,//当前state
      next: null//下一个hook
    };
    if (!fiber.memoizedState) {
      fiber.memoizedState = hook;//第一个hook的话直接赋值给fiber.memoizedState
    } else {
      workInProgressHook.next = hook;//不是第一个的话就加在上一个hook的后面,形成链表
    }
    workInProgressHook = hook;//记录当前工作的hook
    return workInProgressHook;
  }
  function updateWorkInProgressHook() {//update时调用
    let curHook = workInProgressHook;
    workInProgressHook = workInProgressHook.next;//下一个hook
    return curHook;
  }
  function useState(initialState) {
    let hook;
    if (isMount) {
      hook = mountWorkInProgressHook();
      hook.memoizedState = initialState;//初始状态
    } else {
      hook = updateWorkInProgressHook();
    }

    let baseState = hook.memoizedState;//初始状态
    if (hook.queue.pending) {
      let firstUpdate = hook.queue.pending.next;//第一个update

      do {
        const action = firstUpdate.action;
        baseState = action(baseState);
        firstUpdate = firstUpdate.next;//循环update链表
      } while (firstUpdate !== hook.queue.pending);//通过update的action计算state

      hook.queue.pending = null;//重置update链表
    }
    hook.memoizedState = baseState;//赋值新的state

    return [baseState, dispatchAction.bind(null, hook.queue)];//useState的返回
  }

  return {
    useState
  };
})();

function dispatchAction(queue, action) {//触发更新
  const update = {//构建update
    action,
    next: null
  };
  if (queue.pending === null) {
    update.next = update;//update的环状链表
  } else {
    update.next = queue.pending.next;//新的update的next指向前一个update
    queue.pending.next = update;//前一个update的next指向新的update
  }
  queue.pending = update;//更新queue.pending

  isMount = false;//标志mount结束
  workInProgressHook = fiber.memoizedState;//更新workInProgressHook
  schedule();//调度更新
}

function App() {
  let [count, setCount] = Dispatcher.useState(1);
  let [age, setAge] = Dispatcher.useState(10);
  return (
    <>
      <p>Clicked {count} times</p>
      <button onClick={() => setCount(() => count + 1)}> Add count</button>
      <p>Age is {age}</p>
      <button onClick={() => setAge(() => age + 1)}> Add age</button>
    </>
  );
}

function schedule() {
  ReactDOM.render(<App />, document.querySelector("#root"));
}

schedule();

未完待续......