参考博客orz: 传送门
包含useEffect、useState,后续更新diff。
js
// 创建虚拟dom的函数
// 函数组件会被babel最初解析成一个特殊的对象,因为还没有运行函数
// function createElement(type, props, ...children) {
// // console.log(type)
// return {
// type,
// props: {
// ...props,
// children: children.map((child) =>
// typeof child === "object" ? child : createTextElement(child)
// ),
// },
// };
// }
function createElement(type, props, ...children) {
// console.log("debug: ", children, children.flat());
return {
type,
props: {
...(props || {}),
children: children.flat().map(i => typeof i === 'object' && i !== null ? i : createTextElement(i))
}
}
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
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) {
prevProps = prevProps || {};
nextProps = nextProps || {};
//Remove old or changed event listeners
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]);
});
// Remove old properties
Object.keys(prevProps)
.filter(isProperty)
.filter(isGone(prevProps, nextProps))
.forEach((name) => {
dom[name] = "";
});
// Set new or changed properties
Object.keys(nextProps)
.filter(isProperty)
.filter(isNew(prevProps, nextProps))
.forEach((name) => {
dom[name] = nextProps[name];
});
// Add event listeners
Object.keys(nextProps)
.filter(isEvent)
.filter(isNew(prevProps, nextProps))
.forEach((name) => {
const eventType = name.toLowerCase().substring(2);
dom.addEventListener(eventType, nextProps[name]);
});
}
function commitEffects() {
const _ = (fiber) => {
if(!fiber || fiber === null) return;
if(fiber && fiber.hooks && fiber.type instanceof Function) {
fiber.hooks.filter(hk => hk.isEffect === true).forEach(hk => {
let nx = null;
if(hk.cb) {
nx = hk.cb();
hk.cb = null;
}
if(nx) {
if(hk.clean) hk.clean();
hk.clean = nx;
}
});
}
_(fiber.child);
_(fiber.sibling);
};
_(wipRoot);
}
function commitRoot() {
deletions.forEach(commitWork);
commitWork(wipRoot.child);
commitEffects();
currentRoot = wipRoot;
wipRoot = null;
}
function commitWork(fiber) {
if (!fiber) {
return;
}
let domParentFiber = fiber.parent;
// console.log("si: ", fiber.parent)
while (!domParentFiber.dom) {
domParentFiber = domParentFiber.parent;
}
const domParent = domParentFiber.dom; // 找到第一个有真实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") {
commitDeletion(fiber, domParent);
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
function commitDeletion(fiber, domParent) {
if (fiber.dom) {
domParent.removeChild(fiber.dom);
} else {
commitDeletion(fiber.child, domParent);
}
}
// 将element部署到container上,只会在加载页面的时候进行一次。
function render(element, container) {
// console.log("debug: ", element, container);
// console.log("debug: ", currentRoot);
// 初始化工作区的fiber-tree
wipRoot = {
dom: container, // root
props: {
children: [element],
},
alternate: currentRoot,
};
deletions = []; // 需要删除的元素
nextUnitOfWork = wipRoot; // 下一个需要工作的fiber节点
}
let nextUnitOfWork = null; // 下一次工作的目标fiber,初始化为null
let currentRoot = null; // 当前渲染的fiber-dom树
let wipRoot = null; // 正在工作的fiber-dom树
let deletions = null; // 需要删除的元素的数组
// 工作片,最小工作单元
function workLoop(deadline) {
// console.log(deadline);
let shouldYield = false; // 初始化为false,当为ture的时候需要暂停
// 当有下一个工作的单元并且不需要暂停时
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // 执行nextUnitOfWork
shouldYield = deadline.timeRemaining() < 1;
}
if (!nextUnitOfWork && wipRoot) {
commitRoot();
}
requestIdleCallback(workLoop);
}
// 浏览器函数,每次自动运行回调
requestIdleCallback(workLoop);
// 执行fiber任务的函数,返回下一个工作的单元
function performUnitOfWork(fiber) {
const isFunctionComponent = fiber.type instanceof Function;
if (isFunctionComponent) {
// 函数组件:
updateFunctionComponent(fiber);
} else {
// 原始标签
updateHostComponent(fiber);
}
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
}
let wipFiber = null; // 当前工作的wib-fiber
let hookIndex = null;
function updateFunctionComponent(fiber) {
// console.log(fiber);
wipFiber = fiber;
hookIndex = 0;
wipFiber.hooks = [];
// console.log(fiber);
const children = [fiber.type(fiber.props)]; // 函数组件返回的jsx元素
// console.log("debug", children);
// console.log("de: ", children);
reconcileChildren(fiber, children.flat());
}
/**
useEffect(() => {
console.log("执行");
}, [])
*/
function useEffect(f = () => {}, arr = []) {
const oldHook = wipFiber.alternate
&& wipFiber.alternate.hooks
&& wipFiber.alternate.hooks[hookIndex]; // 拿到旧的Effect
let flag = false;
if(!oldHook ||
(oldHook.deps && (oldHook.deps.length !== arr.length
|| oldHook.deps.some((i, idx) => (i !== arr[idx]))
))
) flag = true;
console.log(flag)
const hook = {
deps: arr,
cb: flag ? f : null,
clean: oldHook ? oldHook.clean : null,
isEffect: true
};
if(!wipFiber.hooks) wipFiber.hooks = [];
wipFiber.hooks.push(hook);
hookIndex ++;
}
function useState(initial) {
const oldHook =
wipFiber.alternate &&
wipFiber.alternate.hooks &&
wipFiber.alternate.hooks[hookIndex];
const hook = {
state: oldHook ? oldHook.state : initial,
queue: [],
};
const actions = oldHook ? oldHook.queue : [];
actions.forEach((action) => {
hook.state = action(hook.state);
});
const setState = (action) => {
hook.queue.push(action);
wipRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot,
};
nextUnitOfWork = wipRoot;
deletions = [];
};
wipFiber.hooks.push(hook);
hookIndex++;
return [hook.state, setState];
}
function updateHostComponent(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
reconcileChildren(fiber, fiber.props.children);
}
// 当前的fiber, 新节点的子元素
function reconcileChildren(wipFiber, elements) {
/**
* diff 算法:
* 给定两个列表,每个元素都有自己的key,要求尽可能复用原本的元素。
* 对所有的key-type哈希成数值/带字母的,映射到它们对应的新的节点的索引上面,无key值的只能将其删除重新创建dom
* map, 字典树
*/
let index = 0;
// 通过alternate链接旧的fiber
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
let prevSibling = null;
// console.log("reconcileChildren: ", oldFiber, elements);
// 遍历新的elements,并且去寻找新的
while (index < elements.length || oldFiber != null) {
const element = elements[index]; // 拿到新的节点,注意此时还没有更新
let newFiber = null; // 新fiber的初始化
// 判断两者是否相等
const sameType = oldFiber && element && element.type == oldFiber.type;
// 两者相等的时候,可以复用真实dom,改下props就好。
if (sameType) {
newFiber = {
type: oldFiber.type, // 复用,因为相等
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: "UPDATE", // 标记为UPDATE
};
}
if (element && !sameType) {
// console.log("替换 ", );
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 Didact = {
createElement,
render,
useState,
useEffect
};
/** @jsx Didact.createElement */
function Foo() {
const [s1, setS1] = Didact.useState(2);
return (
<div>
<div onClick={() => setS1((c) => (c += "1"))}>code__ccc</div>
<h2> {s1} </h2>
</div>
);
}
/** @jsx Didact.createElement */
function Counter(props) {
const [state, setState] = Didact.useState(1);
const [isShow, setShow] = Didact.useState(true);
Didact.useEffect(() => {
console.log("count发生了改变", state);
}, [state])
return (
<div>
<h1 onClick={() => setState((c) => c + 1)} style="user-select: none">
Count: {state}
{/* <Foo /> */}
</h1>
{[1, 2, 3, 4].map((i, idx) => <li key={idx}>{i}</li>)}
{null}
<div>
{
isShow ? <div>这个是一个div</div> : null
}
</div>
<button onClick={() => {
setShow((isShow) => {
console.log("执行了");
return !isShow;
});
}}>
按钮
</button>
</div>
);
}
const element = <Counter x={1} />;
// const ele = <p>急啊纠结啊</p>;
const container = document.getElementById("root");
Didact.render(element, container);