迷你 react 迷你 react 和真正的源码有哪些区别呢
在 render
阶段我们遍历了整颗 Fiber
树,在源码中如果节点什么都没改变会命中优化的逻辑,然后跳过这个节点的遍历commit
我们也遍历了整颗 Fiber
树,源码中只遍历带有 effect
的 Fiber
节点,也就是遍历 effectList
每次遍历的时候我们都是新建节点,源码中某些条件会复用节点没有用到优先级
第一步:渲染器和入口函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 节点函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 function createElement (type, props, ...children ) { return { type, props: { ...props, children: children.map((child ) => typeof child === "object" ? child : createTextElement(child) ), }, }; } 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; }
第三步:更新节点函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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 阶段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 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 ) { 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 阶段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function commitRoot ( ) { deletions.forEach(commitWork); commitWork(wipRoot.child); currentRoot = wipRoot; wipRoot = null ; } 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); }
第六步:开始调度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function render (element, container ) { wipRoot = { dom: container, props: { children: [element], }, alternate: currentRoot, }; deletions = []; nextUnitOfWork = wipRoot; } let nextUnitOfWork = null ;let currentRoot = null ;let wipRoot = null ;let deletions = null ;function workLoop (deadline ) { let shouldYield = false ; while (nextUnitOfWork && !shouldYield) { nextUnitOfWork = performUnitOfWork( nextUnitOfWork ); shouldYield = deadline.timeRemaining() < 1 ; } if (!nextUnitOfWork && wipRoot) { commitRoot(); } requestIdleCallback(workLoop); } requestIdleCallback(workLoop);