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");