mini-react原理

2 阅读5分钟
  • 实现一个mini-react
// react -> vdom -> js object

function createDom(fiber) {
  const dom =
    fiber.type === "TEXT_ELEMENT"
      ? document.createTextNode(fiber.props.nodeValue) // 创建文本节点
      : document.createElement(fiber.type); // 创建元素节点
  // 设置props属性
  Object.keys(fiber.props).forEach((key) => {
    // 排除children属性
    if (key !== "children") {
      if (key === "style") {
        Object.keys(fiber.props.style).forEach((styleKey) => {
          dom.style[styleKey] = fiber.props.style[styleKey];
        });
      } else {
        dom[key] = fiber.props[key]; // 设置属性
      }
    }
  });

  //处理children, 不用这种方式,它没法会造成页面的卡顿,同步没法终止
  // const children = el.props.children || [];
  // children.forEach((child) => {
  //   render(child, dom); // 递归渲染
  // });
  return dom;
}

// 创建文本节点
function createTextNode(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

// 创建元素节点
function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => {
        return typeof child === "object" ? child : createTextNode(child); // 如果是文本节点,则创建文本节点
      }),
    },
  };
}

// 并行渲染 concurentMode
// requestIdleCallback 去模拟react中的逻辑
let nextUnitOfWork = null; // 下一个要渲染的节点,这边要想办法设定第一个任务
let wipRoot = null; // 根节点
let currentRoot = null; // 当前根节点
let deletions = null; // 删除的节点

// commit函数
function commitRoot() {
  deletions.forEach(commitWork);
  commitWork(wipRoot.child);
  currentRoot = wipRoot;
  wipRoot = null;
}

function commitWork(fiber) {
  if (!fiber) return;
  let domParentFiber = fiber.parent; // 父节点
  while (!domParentFiber.dom) {
    // 找到最近父节点
    domParentFiber = domParentFiber.parent;
  }
  const domParent = domParentFiber.dom;

  if (fiber.effectTag === "PLACEMENT" && fiber.dom) {
    domParent.appendChild(fiber.dom); // 新增的节点
  } else if (fiber.effectTag === "DELETION") {
    commitDeletion(fiber, domParent); // 删除的节点
    // domParent.removeChild(fiber.dom); // 删除节点,这边不能直接删除,因为可能还有子节点
  } else if (fiber.effectTag === "UPDATE" && fiber.dom) {
    // 本次 fiber dom、上次fiber的属性、本次的属性
    updateDom(fiber.dom, fiber.alternate.props, fiber.props); // 更新节点
  }

  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

function commitDeletion(fiber, domParent) {
  if (fiber.dom) {
    // 正常的非函数的组件
    domParent.removeChild(fiber.dom);
  } else {
    commitDeletion(fiber.child, domParent);
  }
}

function updateDom(dom, prevProps, nextProps) {
  const isEvent = (key) => key.startsWith("on"); // 判断是否是事件
  // 删除已经没有的事件处理函数或者发生变化的事件处理函数
  Object.keys(prevProps)
    .filter(isEvent)
    .filter((key) => !(key in nextProps) || prevProps[key] != nextProps[key])
    .forEach((key) => {
      const eventType = key.toLowerCase().substring(2); // 获取事件类型
      dom.removeEventListener(eventType, prevProps[key]); // 移除事件处理函数
    });
  // 添加事件处理函数
  Object.keys(nextProps)
    .filter(isEvent)
    .filter((key) => prevProps[key] != nextProps[key])
    .forEach((key) => {
      const eventType = key.toLowerCase().substring(2);
      dom.addEventListener(eventType, nextProps[key]); // 添加事件处理函数
    });

  // 更新dom
  // 删除没有的props
  Object.keys(prevProps)
    .filter((key) => key !== "children") // 过滤掉children
    .filter((key) => !(key in nextProps)) // 不存在的属性
    .forEach((key) => {
      dom[key] = "";
    });
  // 更新存在的props
  Object.keys(nextProps)
    .filter((key) => key !== "children") // 过滤掉children
    .filter((key) => !(key in nextProps) || prevProps[key] !== nextProps[key]) // 老的fiber不存在的属性或者存在跟新的fiber不一样
    .forEach((key) => {
      dom[key] = nextProps[key]; // 更新属性
    });
}

function workLoop(deadline) {
  let shouldYield = false; // 是否应该中断
  // 有任务并且有剩余时间
  while (nextUnitOfWork && !shouldYield) {
    console.log("workLoop");
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // 递归渲染
    shouldYield = deadline.timeRemaining() < 1; // 如果剩余时间小于1ms,则中断
  }
  requestIdleCallback(workLoop); // 如果足够时间,请求下一个空闲时间片

  if (!nextUnitOfWork && wipRoot) {
    // 如果任务执行完毕,则提交: 保证异步渲染同步提交
    commitRoot(); // 提交
  }
}
requestIdleCallback(workLoop); // 请求第一个空闲时间片

function updateHostComponent(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber); // 创建dom元素
  }
  reconcileChildren(fiber, fiber.props.children); // 递归渲染子节点
}

let wipFiber = null;
let hookIndex = 0; // 当前hook的索引
function useState(init) {
  const oldHook = wipFiber.alternate && wipFiber.alternate.hooks[hookIndex];
  const hook = {
    state: oldHook ? oldHook.state : init,
    queue: [],
  };
  const actions = oldHook ? oldHook.queue : [];
  actions.forEach((action) => (hook.state = action(hook.state))); // 执行所有action,更新state

  setState = (action) => {
    hook.queue.push(action);
    wipRoot = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      alternate: currentRoot,
    };
    nextUnitOfWork = wipRoot;
    deletions = [];
  };

  wipFiber.hooks.push(hook); // 将hook添加到fiber的hooks数组中
  hookIndex++; // 更新hook的索引
  return [hook.state, setState]; // 返回state
}

function updateFunctionComponent(fiber) {
  wipFiber = fiber;
  hookIndex = 0; // 重置hook的索引
  wipFiber.hooks = []; // 重置hook的数组

  // 执行函数组件,获取组件返回的fiber
  fiber.dom = null; // 函数组件没有dom,所以设置为null
  const children = [fiber.type(fiber.props)]; // 执行函数组件,获取组件返回的fiber
  reconcileChildren(fiber, children); // 递归渲染子节点
}

// 递归渲染
function performUnitOfWork(fiber) {
  // 创建dom元素
  if (!fiber.dom) {
    fiber.dom = createDom(fiber); // 创建dom元素
  }

  // 追加到父节点
  // if (fiber.parent) {
  //   fiber.parent.dom.appendChild(fiber.dom);
  // }

  const isFunctionComponent = typeof fiber.type === "function"; // 是否是函数组件,
  if (isFunctionComponent) {
    updateFunctionComponent(fiber);
  } else {
    updateHostComponent(fiber);
  }
  // 对比下面这一句

  // 给子节点创建fiber
  // const elements = fiber.props.children; // 获取子节点
  // reconcileChildren(fiber, elements); // 新建newFiber,diff算法构建fiber树 对比下面的这里的dom可以复用了

  // 构建fiber树
  // let prevSibling = null; // 前一个兄弟节点
  // let index = 0;
  // while (index < elements.length) {
  //   const element = elements[index];
  //   const newFiber = {
  //     type: element.type, // 保存类型
  //     props: element.props, // 保存属性
  //     parent: fiber, // 保存父节点
  //     dom: null,
  //     alternate: null, // 保存上一个节点
  //     sibling: null,
  //     child: null, // 保存子节点
  //   };

  //   if (index === 0) {
  //     fiber.child = newFiber; // 第一个作为子节点
  //     console.log(fiber.child, "第一个作为子节点");
  //   } else {
  //     prevSibling.sibling = newFiber; // 其它的作为兄弟节点
  //     console.log(prevSibling.sibling, "其它的作为兄弟节点");
  //   }

  //   prevSibling = newFiber; // 更新前一个兄弟节点
  //   index++;
  // }

  // 返回下一个fiber
  if (fiber.child) {
    return fiber.child; // 儿子优先
  }
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling; // 兄弟其次
    }
    nextFiber = nextFiber.parent; // 兄弟节点没有了 -> 父节点最后
  }
}

// diff函数
function reconcileChildren(wipFiber, elements) {
  let index = 0;
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child; // 保存上一个节点的子节点
  let prevSibling = null; // 保存上一个兄弟节点
  let newFiber = null; // 保存新的fiber节点

  while (index < elements.length || oldFiber) {
    let element = elements[index];
    const sameType = oldFiber && element && oldFiber.type === element.type; // 判断类型是否相同
    if (sameType) {
      // 更新
      newFiber = {
        type: oldFiber.type, // 继承老节点的类型
        props: element.props,
        dom: oldFiber.dom, // 继承老节点的dom
        parent: wipFiber, // 保存父节点
        alternate: oldFiber, // 保存老节点
        effectTag: "UPDATE", // 标记更新
      };
    }
    if (element && !sameType) {
      // 新增节点
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null,
        effectTag: "PLACEMENT", // 标记新增
      };
    }
    if (oldFiber && !sameType) {
      // 删除节点
      oldFiber.effectTag = "DELETION"; // 标记删除
      deletions.push(oldFiber); // 添加到删除数组中
    }
    if (oldFiber) {
      oldFiber = oldFiber.sibling; // 因为child只有一个,要遍历孩子节点,所以用sibling
    }

    // 构建fiber树
    if (index === 0) {
      wipFiber.child = newFiber; // 第一个作为子节点
    } else {
      prevSibling.sibling = newFiber; // 其它的作为兄弟节点
    }

    prevSibling = newFiber; // 更新前一个兄弟节点
    index++;
  }
}

// 渲染函数
function render(element, container) {
  console.log(element, container);
  // 初始化第一个任务
  wipRoot = {
    dom: container, // 保存根节点
    props: {
      children: [element],
    },
    alternate: currentRoot, // 保存上一个节点
    sibling: null, // 下一个节点
    child: null, // 第一个子节点
    parent: null, // 父节点
  };
  nextUnitOfWork = wipRoot; // 下一个工作单元
  deletions = []; // 删除的节点
}

// FiberTree: 我们只要把fiber理解成普通的js对象就行了

const React = {
  render,
  createElement,
};

export default React;
  • main.js
// import App1 from "./App.jsx";
import React from "./core/react.js";
// import ReactDOM from "./core/reactDom.js";
// console.log(App1);

const App = React.createElement(
  "div",
  { id: "app", style: { color: "red" } },
  "hello world",
  React.createElement(
    "div",
    { style: { backgroundColor: "yellow" } },
    React.createElement("span", {}, "span1"),
  ),
  React.createElement("div", {}, "span2"),
);

// ReactDOM.createRoot(document.getElementById("root")).render(App);
React.render(App, document.getElementById("root"));