react18 学习(二)


在上一篇创建了根容器和根fiber,在根fiber加了一个空的更新队列。

构建轮替的根fiber

fiber是怎么运作的

为什么要轮替在上一篇已经说过了,这一篇写一下fiber的单项循环链表。

假如我们有一个jsx的dom结构

let element = (
    <div id="A1">
        <div id="B1">
            <div id="C1"></div>
            <div id="C2"></div>
        </div>
        <div id="B2"></div>
    </div>
)

在以前没有用fiber渲染是这样的,这个渲染方式是递归渲染如果数据很多就可能会卡顿。

let vDom = {
    "type": "div",
    "key": "A1",
    "props": {
        "id": "A1",
        "children": [
            {
                "type": "div",
                "key": "B1",
                "props": {
                    "id": "B1",
                    "children": [
                        {
                            "type": "div",
                            "key": "C1",
                            "props": { "id": "C1"},
                        },
                        {
                            "type": "div",
                            "key": "C2",
                            "props": {"id": "C2"},
                        }
                    ]
                },
            },
            {
                "type": "div",
                "key": "B2",
                "props": {"id": "B2"},
            }
        ]
    },
}
// 把vDom一气呵成渲染到页面
function render(element, container) {
	// 把虚拟DOM创建成真实DOM
    let dom = document.createElement(element.type);
    // 遍历属性
    Object.keys(element.props).filter(key => key !== 'children').forEach(key => {
        dom[key] = element.props[key];
    });
    // 把子节点渲染到父节点上
    if(Array.isArray(element.props.children)){
        element.props.children.forEach(child=>render(child,dom));
    }
    // 把真实节点挂载到容器
    container.appendChild(dom);
}
render(element, document.getElementById('root'));

下面是fiber的渲染方式,可以中断、暂停、恢复渲染。深度优先

// 把虚拟DOM构建成Fiber树
let A1 = { type: 'div', props:{id: 'A1'} };
let B1 = { type: 'div', props:{id: 'B1'}, return: A1 };
let B2 = { type: 'div', props:{id: 'B2'}, return: A1 };
let C1 = { type: 'div', props:{id: 'C1'}, return: B1 };
let C2 = { type: 'div', props:{id: 'C2'}, return: B1 };
// A1的子节点是B1
A1.child = B1;
// B1的第一个弟弟是B2
B1.sibling = B2;
B1.child = C1;
C1.sibling = C2;

//下一个工作单元
let nextUnitOfWork = null;

function hasRemainingTime() {
	// 模拟有时间
	return true
}

//render工作循环
function workLoop() {
	// 有下一个fiber任务并且有时间的话
	// 每一个任务执行完都可以放弃,让浏览器执行更高优先级的任务
    while (nextUnitOfWork && hasRemainingTime()) {
        //执行一个任务并返回下一个任务
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
    console.log('render阶段结束');
    //render阶段结束
}
function performUnitOfWork(fiber) {
	// 执行渲染
    let child = beginWork(fiber);
    if(child){
      return child;
    }
    while (fiber) {//如果没有子节点说明当前节点已经完成了渲染工作
        completeUnitOfWork(fiber);//可以结束此fiber的渲染了 
        if (fiber.sibling) {//如果它有弟弟就返回弟弟
            return fiber.sibling;
        }
        fiber = fiber.return;//如果没有弟弟让爸爸完成,然后找叔叔
    }
}
function beginWork(fiber) {
    console.log('beginWork', fiber.props.id);
    // 工作完之后返回第一个子节点
    return fiber.child;
}
function completeUnitOfWork(fiber) {
	// 标记完成这个fiber节点
    console.log('completeUnitOfWork', fiber.props.id);
}
nextUnitOfWork = A1;
workLoop();

队列的单向链表

预热完成,现在正式开始写构建的代码,先在main.jsx添加一行熟悉的代码

root.render(element)

继续上一篇文章,找到ReactDOMRoot.js文件,给ReactDOMRoot函数对象的原型上添加一个render方法
然后给fiber创建渲染的队列

ReactDOMRoot.js

import { createContainer, updateContainer } from "react-reconciler/src/ReactFiberReconciler";

ReactDOMRoot.prototype.render = function (children) {
  // 1. 获取根容器
  const root = this._internalRoot;
  updateContainer(children, root);
};

ReactFiberReconciler.js

import { createUpdate, enqueueUpdate } from "./ReactFiberClassUpdateQueue";
/**
 * 更新容器, 把虚拟DOM变成真实DOM 插入到container容器中
 * @param {*} element 虚拟DOM
 * @param {*} container 容器   FiberRootNode
 */
export function updateContainer(element, container) {
  // 获取根fiber
  const current = container.current;
  // 2. 创建更新任务
  const update = createUpdate();
  // 需要更新的虚拟DOM
  // 这个更新任务是整个虚拟dom
  update.payload = {
    element,
  };
  // 3. 把此更新任务对象添加到current这个根Fiber的更新队列里
  let root = enqueueUpdate(current, update);
  console.log(root);
}

ReactFiberClassUpdateQueue.js

import { markUpdateLaneFromFiberToRoot } from "./ReactFiberConcurrentUpdate";
// 更新状态
export const UpdateState = 0;

// 创建更新任务
export function createUpdate() {
  const update = { tag: UpdateState };
  return update;
}

export function enqueueUpdate(fiber, update) {
  // 获取根fiber的更新队列 (上一篇最后加的)
  const updateQueue = fiber.updateQueue;
  // 获取等待执行的任务 (初始化时没有)
  const pending = updateQueue.shared.pending;
  // 说明是初始化的状态
  if (pending === null) {
    // 让下一个更新任务指向自己
    update.next = update;
  } else {
    // 让当前更新的 next 指向下一个更新
    update.next = pending.next;
    // pending 永远指向最后一个更新
    pending.next = update;
  }
  // 让等待更新指向当前 update 开始更新
  updateQueue.shared.pending = update;
  // 4. 从当前的fiber到返回找到并返回根节点
  return markUpdateLaneFromFiberToRoot(fiber);
}

单向循环链表在这里插入图片描述

冒泡获取根节点容器

ReactFiberConcurrentUpdate.js

import { HostRoot } from "./ReactWorkTags";

/**
 * 本来此文件要处理更新优先级问题,把不同的fiber优先级冒泡一路标记到根节点。
 * 目前现在值实现向上冒泡找到根节点
 * @param {*} sourceFiber
 */
export function markUpdateLaneFromFiberToRoot(sourceFiber) {
  // 当前fiber
  let node = sourceFiber;
  // 父fiber
  let parent = sourceFiber.return;
  while (parent !== null) {
    // 如果有父fiber
    node = parent;
    parent = parent.return;
  }
  // 一直找到parent为null
  if (node.tag === HostRoot) {
    console.log(node.stateNode);
    return node.stateNode;
  }
  return null;
}

调度更新

更新对象已经添加到根fiber的更新队列上,现在要开始更新了。

ReactFiberReconciler.js

import { scheduleUpdateOnFiber } from "./ReactFiberWorkLoop";

export function updateContainer(element, container) {
  // ...
  // 调度更新
  scheduleUpdateOnFiber(root);
}

ReactFiberWorkLoop.js

import { scheduleCallback } from "scheduler";
// 计划更新root
export function scheduleUpdateOnFiber(root) {
  // 确保调度执行root上的更新
  ensureRootIsScheduled(root);
}

function ensureRootIsScheduled(root) {
  // 告诉浏览器要执行performConcurrentWorkOnRoot 参数定死为root
  scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}

function performConcurrentWorkOnRoot(root) {
  console.log(root, 'performConcurrentWorkOnRoot');
}

src/scheduler/index.js

export * from "./src/forks/Scheduler";

forks/Scheduler.js

// 此处后面会实现优先级队列
export function scheduleCallback(callback) {
  requestIdleCallback(callback);
}

在这里插入图片描述

工作循环

在这里插入图片描述

引用上一章提到的一张图片,在上一章已经创建好一个根节点容器和一个空的根fiber(黑色部分),在图中看到还有一个正在构建中的根fiber
根节点的current指的是当前的根fiber,是会和构建中的根fiber轮替工作(双缓冲),现在需要构建一个新的根fiber并且把fiber树写在里面。
一个是表示当前页面已经渲染完成的fiber树,一个是正在构建中还没有生效、更没有更新到页面的fiber树

建立新的hostRootFiber

ReactFiberWorkLoop.js

import { creatWorkInProgress } from "./ReactFiber";

// 正在进行中的工作
let workInProgress = null

//...

/**
 * (被告知浏览器确保执行的函数)
 * 根据当前的fiber节点构建fiber树, 创建真实的dom节点, 插入到容器
 * @param {*} root
 */
function performConcurrentWorkOnRoot(root) {
  // 1. 初次渲染的时候以同步方式渲染根节点, 因为要尽快展示 (初始化)
  renderRootSync(root);
}

function prepareFreshStack(root) {
  // 5. 根据老fiber构建新fiber (初始化)
  workInProgress = creatWorkInProgress(root.current);
}

function renderRootSync(root) {
  // 2. 先构建了一个空的栈
  prepareFreshStack(root);
}

ReactFiber.js

/**
 * 根据老fiber和新的属性构建新fiber
 * @param {*} current 老fiber
 * @param {*} pendingProps 新的属性
 */
export function creatWorkInProgress(current, pendingProps) {
  // 3. 拿到老fiber的轮替 第一次没有 (初始化)
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    // 4. 如果没有, 创建一个新的根fiber (初始化)
    workInProgress = createFiber(current.tag, pendingProps, current.key);
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;
    // 双向指针 互相轮替
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    // 如果有, 说明是更新, 只更改属性就可以复用
    workInProgress.pendingProps = current.pendingProps;
    workInProgress.type = current.type;
    // 清空副作用标识
    workInProgress.flags = NoFlags;
    workInProgress.subtreeFlags = NoFlags;
    // 更新就不用双向指针了
  }
  // 复制属性
  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;
  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index
  return workInProgress
}
执行工作单元

然后在新的根fiber里构建更新fiber树
ReactFiberWorkLoop.js

import { beginWork } from "./ReactFiberBeginWork";

// ...

function renderRootSync(root) {
  // 先构建了一个空的栈
  prepareFreshStack(root);
  // 1. 现在的 workInProgress 是新的根fiber
  workLoopSync();
}

// 工作同步循环
function workLoopSync() {
  while (workInProgress !== null) {
    // 2. 执行工作单元
    performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(unitOfWork) {
  // 3. 通过新fiber获取老fiber
  const current = unitOfWork.alternate;
  // 4. 开始工作, 完成当前fiber的子fiber链表构建后
  const next = beginWork(current, unitOfWork);
  // 把等待生效的属性变成已经生效的
  unitOfWork.memoizeProps = unitOfWork.pendingProps;
  if (next === null) {
    // 说明已经完成
    // 完成工作单元
    // completeUnitOfWork(); // 这个方法之后写 先模拟一下完成工作
    workInProgress = null;
  } else {
    // 如果有子节点就成为下一个工作单元
    workInProgress = next;
  }
}

ReactFiberBeginWork.js

import { HostComponent, HostRoot, HostText } from "./ReactWorkTags";
import { processUpdateQueue } from "./ReactFiberClassUpdateQueue";

function updateHostRoot(current, workInProgress) {
  // 需要知道它的子虚拟dom, 知道它的儿子的虚拟dom信息
  // 之前在根fiber的更新队列加的虚拟dom, 可以在这获取
  processUpdateQueue(workInProgress); // workInProgress.memoizedState = { element }
  const nextState = workInProgress.memoizedState;
  // 拿到虚拟dom
  const nextChildren = nextState.element;
  // 协调子节点 (DOM-DIFF)
  reconcileChildren(current, workInProgress, nextChildren);
  // 返回根据子节点创建的子fiber节点返回出去
  return workInProgress.child; // { tag: 5, type: 'h1' }
}

function updateHostComponents(current, workInProgress) {}

/**
 * 5. 根据 `新的` 虚拟dom去构建  `新的` fiber链表
 * @param {*} current 老fiber
 * @param {*} workInProgress 新fiber
 * @returns 下一个工作单元
 */
export function beginWork(current, workInProgress) {
  console.log("beginWork", workInProgress);
  // 6 判断类型不同处理方式返回子节点或者弟弟
  switch (workInProgress.tag) {
    case HostRoot:
      return updateHostRoot(current, workInProgress);

    case HostComponent:
      return updateHostComponents(current, workInProgress);

    // 纯文本没有子节点
    case HostText:
      return null;

    default:
      return null;
  }
}
获取更新队列的虚拟dom

写上一步引入的processUpdateQueue方法
ReactFiberClassUpdateQueue.js

import { assign } from "shared/assign";

/**
 * 根据老状态和更新队列的更新计算最新的状态
 * @param {*} workInProgress 要计算的fiber
 */
export function processUpdateQueue(workInProgress) {
  // 拿到更新队列
  const queue = workInProgress.updateQueue;
  // 等待生效的队列
  const pendingQueue = queue.shared.pending;
  // 如果有更新, 或者更新队列里有内容
  if (pendingQueue !== null) {
    // 清除等待生效的更新 因为在这就要使用了可以清除了
    queue.shared.pending = null;
    // 获取最后一个等待生效的更新   // update = { payload: { element: 'h1' }}
    const lastPendingUpdate = pendingQueue;
    // 第一个等待生效的更新
    const firstPendingUpdate = pendingQueue.next;
    // 把更新链表剪开, 变成单向链表
    lastPendingUpdate.next = null;
    // 获取老状态 (会不停更新和计算赋值新状态, 所以起名newState)
    let newState = workInProgress.memoizedState;
    let update = firstPendingUpdate;
    while (update) {
      // 根据老状态和更新计算新状态
      newState = getStateFromUpdate(update, newState);
      update = update.next;
    }
    // 把最终计算到的状态赋值给 memoizedState
    workInProgress.memoizedState = newState;
  }
}

/**
 * 根据老状态和更新, 计算新状态
 * @param {*} update 更新
 * @param {*} prevState 上一个状态
 * @returns 新状态
 */
function getStateFromUpdate(update, prevState) {
  switch (update.tag) {
    case UpdateState:
      const { payload } = update;
      return assign({}, prevState, payload);
  }
}
根据子虚拟dom创建子fiber节点

上上步还有一个reconcileChildren没有定义
ReactFiberBeginWork.js

import { mountChildFibers, reconcileChildFibers } from "./ReactChildFiber";

/**
 * 根据新的虚拟dom生成新的fiber链表
 * @param {*} current 老的父fiber
 * @param {*} workInProgress 新的父fiber
 * @param {*} nextChildren 新的子虚拟dom
 */
function reconcileChildren(current, workInProgress, nextChildren) {
  // 如果此新fiber没有老fiber, 说明是新创建的
  if (current === null) {
    // 挂在子fiber
    workInProgress.child = mountChildFibers(workInProgress, null, next);
  } else {
    // 更新:  协调子fiber列表 需要做DOM-DIFF   (初始化时的根fiber是有老fiber的(一开始创建的))
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren
    );
  }
}

ReactChildFiber.js

import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";

/**
 *
 * @param {*} shouldTrackSideEffect 是否跟踪副作用
 * @returns
 */
function createChildReconciler(shouldTrackSideEffect) {
  function reconcileSingleElement(returnFiber, currentFirstFiber, element) {
    // 因为我们实现的是初次挂载, 老节点currentFirstFiber是没有的, 所以可以直接根据虚拟dom创建fiber节点
    const created = createFiberFromElement(element);
    created.return = returnFiber;
    return created;
  }
  /**
   * 比较子fiber  (DOM-DIFF) 就是用老的fiber链表和新的虚拟dom进行比较
   * @param {*} returnFiber 新父fiber
   * @param {*} currentFirstFiber 当前的第一个子fiber(老fiber的第一个儿子)
   * @param {*} newChild 新的子虚拟dom
   */
  function reconcileChildFibers(returnFiber, currentFirstFiber, newChild) {
    // 现在暂时只考虑新的节点只有一个的情况
    if (typeof newChild === "object" && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          return reconcileSingleElement(
            returnFiber,
            currentFirstFiber,
            newChild
          );

        default:
          break;
      }
    }
  }
  return reconcileChildFibers;
}

// 有老父fiber 更新
export const reconcileChildFibers = createChildReconciler(true);
// 没有老的父fiber  更新
export const mountChildFibers = createChildReconciler(false);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值