写点什么

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

作者:zz1998
  • 2021 年 12 月 08 日
  • 本文字数:5420 字

    阅读完需:约 18 分钟

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 节点函数


/创建elementfunction 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: [], }, };}
//创建domfunction 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;}
//操作真实domfunction 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);
复制代码


完整代码


//创建elementfunction 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: [], }, };}
//创建domfunction 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;}
//操作真实domfunction 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");
复制代码


用户头像

zz1998

关注

还未添加个人签名 2021.06.26 加入

还未添加个人简介

评论

发布
暂无评论
react源码解析19.手写迷你版react