彻底搞懂fiber架构, 手写简易版 React

vdom:

16v 前: vdome -> 递归 diff render;
递归渲染时做 diff 来确定增删改以及创建 dom

  • 通过children关联父子节点
  • 递归diff 不可中断
  • 影响性能 影响用户体验

react的setState会渲染整个页面,当应用vdom过于庞大,层级过深,计算量就会增大,在进行一些用户操作如输入框输入时,会导致diff和渲染事件过长 导致页面卡顿影响用户体验

fiber框架:

16v 后:vdome -> filber -> render(commit)
fiber链表结构,可以打断,这样就可以通过requestIdleCallback来空闲调度reconciler,这样不断的循环,直到处理完所有的vdom转fiber的reconciler,就开始commit,也就是更新到 dom。

  • 通过child 关联第一个子节点,通过sibling串联 所有节点都可以return父节点
  • 空闲时间执行 循环转换
  • 可中断 优先执行 用户交互 页面滚动 尺寸变更
  • 设置更新优先级
    • 生命周期、用户操作
    • 交互时间
    • 数据请求
      在这里插入图片描述

scheduler(调度器)

通过scheduler去循环调用chrome的apirequestIdleCallback 查询当前浏览器是否空闲,空闲的话执行reconciler(将vdome转fiber);

reconciler(调节器)

  • vdom转fiber,并提前创建对应的dome节点、做diff 将effectTag放到effectList中
  • 创建根fiber节点, 并且下一个处理的fiber指向这个节点,在下次scheduler就会调度这个fiber节点,开始reconciler
  • 当前的fiber节点,然后按照顺序 处理child、sibling 处理完成 return fiber节点, 不断调度;

commit(render)

  • 把fiber链表,添加到dom中,通过effectList对dom进行增删改
  • reconciler阶段对应fiber节点已创建,所以render会比之前vdom渲染会更快

requestIdleCallback(callback[, options])

  • callback: 空闲时间需要执行的任务;回接收一个IdleDeadline对象{ didTimeout: boolean(任务是否超时), timeRemaining(): 当前帧剩余时间(留给任务的空闲时间) }
  • options: timeout; 表示超过这个时间后,如果任务还没执行,将强制执行 不必等待空闲。
// 简易版 react
// 记录当前fiber节点、根fiber节点
let nextFiberReconcileWork = null;
let wipRoot = null;
function workLoop(deadline) {
  let shouldYield = false;
  while (nextFiberReconcileWork && !shouldYield) {
    nextFiberReconcileWork = performNextWork(nextFiberReconcileWork); // performNextWork reconciler
    shouldYield = deadline.timeRemaining() < 1; // 是否可以渲染
  }
  // 如果节点为空就全部commit
  if (!nextFiberReconcileWork) {
    commitRoot();
  }
  window.requestIdleCallback(workLoop);
}
window.requestIdleCallback(workLoop); // requestIdleCallback chrome api

// reconciler 每次执行
function performNextWork(fiber) {
  reconciler(fiber);
  // 如果节点有child返回fiber
  if (fiber.child) {
    return fiber.child;
  }
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    nextFiber = nextFiber.return;
  }
}

// reconcile
// 创建根节点 赋值给wipRoot 并把下一个处理的fiber节点指向它
function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
  };
  nextFiberReconcileWork = wipRoot;
}

function reconciler(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber);
  }
  // fiber.props.children: vdom子节点 把之前的vdom转成child、sibling、return 串联的链表
  reconcilerChildren(fiber, fiber.props.children);
}

// 循环处理每个vdom的elements, index为0 child串联,否则sibling串联.  创建出的节点 都要用return指向父节点
function reconcilerChildren(wipFiber, elements) {
  let index = 0;
  let prevSibling = null;

  while (index < elements.length) {
    const element = elements[index];
    let newFiber = {
      type: element.type,
      name: element.name,
      props: element.props,
      dom: null,
      return: wipFiber,
      effectTag: "PLACEMENT",
    };
    if (index === 0) {
      wipFiber.child = newFiber;
    } else if (element) {
      prevSibling.sibling = newFiber;
    }
    prevSibling = newFiber;
    index++;
  }
}

// commit
function commitRoot() {
  commitWork(wipRoot.child);
  // 不在需要调度 设为空
  wipRoot = null;
}

function commitWork(fiber) {
  if (!fiber) {
    return;
  }

  let domParentFiber = fiber.return;
  while (!domParentFiber.data) {
    domParentFiber = domParentFiber.return;
  }
  const domParent = domParentFiber.dom;
  if (fiber.effectTag === "PLACEMENT" && fiber.dom !== null) {
    domParent.appendChild(fiber.dom);
  }
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

//根据类型创建元素,然后设置属性; 属性分别处理 style、文本节点的 value、事件监听器:
function createDom(fiber) {
  const dom =
    fiber.type === "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(fiber.type);
  for (const prop in fiber.props) {
    setAttribute(dom, prop, fiber.props[prop]);
  }
  return dom;
}

// 判断是否为事件监听器
function isEventListenerAttr(key, value) {
  return typeof value === "function" && key.startsWith("on");
}

// 判断是否为style
function isStyleAttr(key, value) {
  return key === 'style' && typeof value === 'object';
}

// 判断是否为value
function isPlainAttr(key, value) {
  return typeof value !== 'object' && typeof value !== 'function';
}

function setAttribute(dome, key, value) {
  if(key === 'children'){
    return;
  }
  if(key === 'nodeValue'){
    dom.textContent = value;
  } else if(isEventListenerAttr(key, value)){
    const eventType = key.slice(2).toLowerCase();
    document.addEventListener(eventType, value);
  } else if(isStyleAttr(key, value)){
    Object.assign(dom.style, value);
  } else if(isPlainAttr(key, value)){
    dom.setAttribute(key, value);
  }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值