mini-react(三)实现统一提交&Function Component

上一节中,我们通过 requestIdleCallback 利用了空余时间去完成每个task。这种方式存在一个问题,当中途没有空余时间时,用户可能会看到渲染一半的dom。

我们可以采用统一提交的方式去解决这个问题,先处理链表,最后再统一添加到屏幕中。

实现统一提交

实现统一提交我们需要搞清楚两个关键点:

  1. 何时处理完链表
  2. 根结点位置

根据我们performWorkOfUnit的逻辑:

js 复制代码
  return fiber.child
    ? fiber.child
    : fiber.sibling
    ? fiber.sibling
    : fiber.parent.sibling
    ? fiber.parent.sibling
    : null;

当其return null时,我们的链表就结束了,即 nextWorkOfUnit===null

而根结点其实就是我们在render函数中初始化的 nextWorkOfUnit

知道了这两个关键点我们就可以轻松地实现统一提交了。

首先先注释掉 performWorkOfUnit 中添加dom的逻辑

diff 复制代码
- fiber.parent.dom.append(dom);
+ //fiber.parent.dom.append(dom);

定义一个变量 root 代表根结点,并在render函数中赋值

js 复制代码
let root = null;
function render(el, container) {
  nextWorkOfUnit = {
    dom: container,
    props: {
      children: [el],
    },
  };
  root = nextWorkOfUnit;
}

然后实现添加dom的函数commitWork,为了更易理解,我们再创建一个函数commitRoot去调用一下

js 复制代码
 function commitRoot() {
  commitWork(root.child);
}

function commitWork(fiber) {
  if (!fiber) return;
  fiber.parent.dom.append(fiber.dom);
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

最终在链表结束时调用我们的commitRoot,为了保证只调用一次,我们再把root设置为null

diff 复制代码
function workLoop(deadline) {
  let shouldYield = false;
  while (!shouldYield && nextWorkOfUnit) {
    shouldYield = deadline.timeRemaining() < 1;
    nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit);
  }

+  if (!nextWorkOfUnit && root) {
+    commitRoot();
+    root = null;
+  }

  requestIdleCallback(workLoop);
}

这样我们就实现了统一提交,解决了无空余时间的问题。

实现Function Component

目前我们的mini-react还不支持Function Component,组件是对象的形式:

js 复制代码
const App = (
  <div className="app">
    hello-mini-react
    <CounterContainer />
  </div>
);
ReactDOM.createRoot(document.querySelector("#root")).render(App);

如果我们改成Function Component的形式,浏览器就会报错:

js 复制代码
function App() {
  return (
    <div className="app">
      hello-mini-react
    </div>
  );
}
ReactDOM.createRoot(document.querySelector("#root")).render(<App />);

我们发现传入的是一个函数,所以我们可以进行一个判断,如果传入的是函数就调用一下

js 复制代码
const isFunctionComponent = typeof fiber.type === "function";
  if (!isFunctionComponent) {
    updateHostComponent(fiber);
  } else {
    updateFunctionComponent(fiber);
  }

因为传入的是函数,我们便不需要根据其type去创建dom了,之后我们需要对children进行处理,调用后返回的是一个对象而不是数组,我们需要将其包裹一下:

js 复制代码
function updateHostComponent(fiber) {
  if (!fiber.dom) {
    const dom = (fiber.dom = createDom(fiber.type));
    updateProps(dom, fiber.props);
  }
  const children = fiber.props.children;
  initChildren(fiber, children);
}

function updateFunctionComponent(fiber) {
  const children = [fiber.type(fiber.props)];
  initChildren(fiber, children);
}

Function Component并无dom结构,所以我们需要递归去寻找其父节点,将dom添加到父节点上

js 复制代码
function commitWork(fiber) {
  if (!fiber) return;
  let fiberParent = fiber.parent;
  while (!fiberParent.dom) {
    fiberParent = fiberParent.parent;
  }
  if (fiber.dom) {
    fiberParent.dom.append(fiber.dom);
  }
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

要支持props我们只需将其作为参数传入到函数中即可,但我们之前的判断逻辑只支持了string类型,如果传入number类型就会报错

js 复制代码
function Counter({ num }) {
  return <div className="counter">Count:{num}</div>;
}

修改一下判断逻辑,支持number类型:

js 复制代码
function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => {
        const isTextNode =
          typeof child === "string" || typeof child === "number";
        return isTextNode ? createTextNode(child) : child;
      }),
    },
  };
}

当我们嵌套使用Function Component时, 会出现只能渲染一个的情况

js 复制代码
function Counter({ num }) {
  return <div className="counter">Count:{num}</div>;
}
function CounterContainer() {
  return (
    <div>
      <Counter num={10} />
      <Counter num={20} />
    </div>
  );
}
function App() {
  return (
    <div className="app">
      hello-mini-react
      <CounterContainer />
    </div>
  );
}

原因是<Counter num={10} />parentdiv,找不到其parent.sibling

所以我们要进行处理,找到真正的parent.sibling也就是<Counter num={20} />

js 复制代码
if (fiber.child) {
    return fiber.child;
}

if (fiber.sibling) {
    return fiber.sibling;
}

let nextFiber = fiber.parent;
    while (nextFiber) {
    if (nextFiber.sibling) return nextFiber.sibling;
    nextFiber = nextFiber.parent;
}

这样我们就实现了对Function Component的支持

参考代码见github

相关推荐
敲敲了个代码5 小时前
从硬编码到 Schema 推断:前端表单开发的工程化转型
前端·javascript·vue.js·学习·面试·职场和发展·前端框架
dly_blog7 小时前
Vue 响应式陷阱与解决方案(第19节)
前端·javascript·vue.js
消失的旧时光-19437 小时前
401 自动刷新 Token 的完整架构设计(Dio 实战版)
开发语言·前端·javascript
console.log('npc')7 小时前
Table,vue3在父组件调用子组件columns列的方法展示弹窗文件预览效果
前端·javascript·vue.js
用户47949283569157 小时前
React Hooks 的“天条”:为啥绝对不能写在 if 语句里?
前端·react.js
我命由我123458 小时前
SVG - SVG 引入(SVG 概述、SVG 基本使用、SVG 使用 CSS、SVG 使用 JavaScript、SVG 实例实操)
开发语言·前端·javascript·css·学习·ecmascript·学习方法
用户47949283569158 小时前
给客户做私有化部署,我是如何优雅搞定 NPM 依赖管理的?
前端·后端·程序员
C_心欲无痕8 小时前
vue3 - markRaw标记为非响应式对象
前端·javascript·vue.js
qingyun9899 小时前
深度优先遍历:JavaScript递归查找树形数据结构中的节点标签
前端·javascript·数据结构
熬夜敲代码的小N9 小时前
Vue (Official)重磅更新!Vue Language Tools 3.2功能一览!
前端·javascript·vue.js