手写自己的小型react
参考博客orz: 传送门
包含useEffect、useState,后续更新diff。
// 创建虚拟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 listenersObject.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 propertiesObject.keys(prevProps).filter(isProperty).filter(isGone(prevProps, nextProps)).forEach((name) => {dom[name] = "";});// Set new or changed propertiesObject.keys(nextProps).filter(isProperty).filter(isNew(prevProps, nextProps)).forEach((name) => {dom[name] = nextProps[name];});// Add event listenersObject.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-treewipRoot = {dom: container, // rootprops: {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); // 执行nextUnitOfWorkshouldYield = 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];  // 拿到旧的Effectlet 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链接旧的fiberlet 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);