关于react.js:react源码解析19手写迷你版react

4次阅读

共计 8175 个字符,预计需要花费 21 分钟才能阅读完成。

react 源码解析 19. 手写迷你版 react

视频解说(高效学习):进入学习

往期文章:

1. 开篇介绍和面试题

2.react 的设计理念

3.react 源码架构

4. 源码目录构造和调试

5.jsx& 外围 api

6.legacy 和 concurrent 模式入口函数

7.Fiber 架构

8.render 阶段

9.diff 算法

10.commit 阶段

11. 生命周期

12. 状态更新流程

13.hooks 源码

14. 手写 hooks

15.scheduler&Lane

16.concurrent 模式

17.context

18 事件零碎

19. 手写迷你版 react

20. 总结 & 第一章的面试题解答

迷你 react 和真正的源码有哪些区别呢

  • 在 render 阶段咱们遍历了整颗 Fiber 树,在源码中如果节点什么都没扭转会命中优化的逻辑,而后跳过这个节点的遍历
  • commit 咱们也遍历了整颗 Fiber 树,源码中只遍历带有 effect 的 Fiber 节点,也就是遍历 effectList
  • 每次遍历的时候咱们都是新建节点,源码中某些条件会复用节点
  • 没有用到优先级

第一步:渲染器和入口函数

const React = {
  createElement,
  render,
};

const container = document.getElementById("root");

const updateValue = (e) => {rerender(e.target.value);
};

const rerender = (value) => {
  const element = (
    <div>
      <input onInput={updateValue} value={value} />
      <h2>Hello {value}</h2>
    </div>
  );
  React.render(element, container);
};

rerender("World");

第二步:创立 dom 节点函数

/ 创立 element
function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => (typeof child === "object" ? child : createTextElement(child))),
    },
  };
}

// 创立 text 类型
function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],},
  };
}

// 创立 dom
function createDom(fiber) {const dom = fiber.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(fiber.type);

  updateDom(dom, {}, fiber.props);

  return dom;
}

第三步:更新节点函数

const isEvent = (key) => key.startsWith("on");
const isProperty = (key) => key !== "children" && !isEvent(key);
const isNew = (prev, next) => (key) => prev[key] !== next[key];
const isGone = (prev, next) => (key) => !(key in next);

// 更新节点属性
function updateDom(dom, prevProps, nextProps) {
  // 删除老的事件
  Object.keys(prevProps)
    .filter(isEvent)
    .filter((key) => !(key in nextProps) || isNew(prevProps, nextProps)(key))
    .forEach((name) => {const eventType = name.toLowerCase().substring(2);
      dom.removeEventListener(eventType, prevProps[name]);
    });

  // 删除旧属性
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach((name) => {dom[name] = "";
    });

  // 设置新属性
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) => {dom[name] = nextProps[name];
    });

  // 减少新事件
  Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) => {const eventType = name.toLowerCase().substring(2);
      dom.addEventListener(eventType, nextProps[name]);
    });
}

第四步:render 阶段

//render 阶段
function performUnitOfWork(fiber) {if (!fiber.dom) {fiber.dom = createDom(fiber);
  }

  const elements = fiber.props.children;
  reconcileChildren(fiber, elements);

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

// 调协节点
function reconcileChildren(wipFiber, elements) {
  let index = 0;
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
  let prevSibling = null;
  while (index < elements.length || (oldFiber !== null && oldFiber !== undefined)) {const element = elements[index];
    let newFiber = null;

    const sameType = oldFiber && element && element.type === oldFiber.type;

    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.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;}

    if (index === 0) {wipFiber.child = newFiber;} else if (element) {prevSibling.sibling = newFiber;}
    prevSibling = newFiber;
    index++;
  }
}

第五步:commit 阶段

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

// 操作实在 dom
function commitWork(fiber) {if (!fiber) {return;}

  const domParent = fiber.parent.dom;
  if (fiber.effectTag === "PLACEMENT" && fiber.dom !== null) {domParent.appendChild(fiber.dom);
  } else if (fiber.effectTag === "UPDATE" && fiber.dom !== null) {updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  } else if (fiber.effectTag === "DELETION") {domParent.removeChild(fiber.dom);
  }

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

第六步:开始调度

// 渲染开始的入口
function render(element, container) {
  wipRoot = {
    dom: container,
    props: {children: [element],
    },
    alternate: currentRoot,
  };
  deletions = [];
  nextUnitOfWork = wipRoot;
}

// 调度函数
function workLoop(deadline) {
  let shouldYield = false;
  while (nextUnitOfWork && !shouldYield) {
    //render 阶段
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1;}

  if (!nextUnitOfWork && wipRoot) {commitRoot(); //commit 阶段
  }

  requestIdleCallback(workLoop); // 闲暇调度
}

requestIdleCallback(workLoop);

残缺代码

// 创立 element
function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => (typeof child === "object" ? child : createTextElement(child))),
    },
  };
}

// 创立 text 类型
function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],},
  };
}

// 创立 dom
function createDom(fiber) {const dom = fiber.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(fiber.type);

  updateDom(dom, {}, fiber.props);

  return dom;
}

const isEvent = (key) => key.startsWith("on");
const isProperty = (key) => key !== "children" && !isEvent(key);
const isNew = (prev, next) => (key) => prev[key] !== next[key];
const isGone = (prev, next) => (key) => !(key in next);

// 更新节点属性
function updateDom(dom, prevProps, nextProps) {
  // 删除老的事件
  Object.keys(prevProps)
    .filter(isEvent)
    .filter((key) => !(key in nextProps) || isNew(prevProps, nextProps)(key))
    .forEach((name) => {const eventType = name.toLowerCase().substring(2);
      dom.removeEventListener(eventType, prevProps[name]);
    });

  // 删除旧属性
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach((name) => {dom[name] = "";
    });

  // 设置新属性
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) => {dom[name] = nextProps[name];
    });

  // 减少新事件
  Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) => {const eventType = name.toLowerCase().substring(2);
      dom.addEventListener(eventType, nextProps[name]);
    });
}

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

// 操作实在 dom
function commitWork(fiber) {if (!fiber) {return;}

  const domParent = fiber.parent.dom;
  if (fiber.effectTag === "PLACEMENT" && fiber.dom !== null) {domParent.appendChild(fiber.dom);
  } else if (fiber.effectTag === "UPDATE" && fiber.dom !== null) {updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  } else if (fiber.effectTag === "DELETION") {domParent.removeChild(fiber.dom);
  }

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

let nextUnitOfWork = null;
let currentRoot = null;
let wipRoot = null;
let deletions = null;

// 渲染开始的入口
function render(element, container) {
  wipRoot = {
    dom: container,
    props: {children: [element],
    },
    alternate: currentRoot,
  };
  deletions = [];
  nextUnitOfWork = wipRoot;
}

// 调度函数
function workLoop(deadline) {
  let shouldYield = false;
  while (nextUnitOfWork && !shouldYield) {
    //render 阶段
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1;}

  if (!nextUnitOfWork && wipRoot) {commitRoot(); //commit 阶段
  }

  requestIdleCallback(workLoop); // 闲暇调度
}

requestIdleCallback(workLoop);

//render 阶段
function performUnitOfWork(fiber) {if (!fiber.dom) {fiber.dom = createDom(fiber);
  }

  const elements = fiber.props.children;
  reconcileChildren(fiber, elements);

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

// 调协节点
function reconcileChildren(wipFiber, elements) {
  let index = 0;
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
  let prevSibling = null;
  while (index < elements.length || (oldFiber !== null && oldFiber !== undefined)) {const element = elements[index];
    let newFiber = null;

    const sameType = oldFiber && element && element.type === oldFiber.type;

    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.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;}

    if (index === 0) {wipFiber.child = newFiber;} else if (element) {prevSibling.sibling = newFiber;}
    prevSibling = newFiber;
    index++;
  }
}

const React = {
  createElement,
  render,
};

const container = document.getElementById("root");

const updateValue = (e) => {rerender(e.target.value);
};

const rerender = (value) => {
  const element = (
    <div>
      <input onInput={updateValue} value={value} />
      <h2>Hello {value}</h2>
    </div>
  );
  React.render(element, container);
};

rerender("World");
正文完
 0