徒手写一个基于 Fiber 和 Hooks 的 React Likely Framework
完成了基础 Fiber、Hooks 的功能,目的只是为了学习 React 相关知识,从底层开始了解 React 的运行机制
fiber 架构就分为了 schdule、reconcile(vdom 转 fiber)、commit(更新到 dom)三个阶段,vdom 转 fiber 的过程叫做 reconcile,最后增删改真实 dom 的过程叫做 commit。
VDOM 到 Fiber 的过程,Hooks 状态保存到 FC Fiber 节点,hooks 状态存放在 FC 的 FiberRoot 节点上。
/** * 1. 为当前 hook 设置索引位置,新增or使用上一次 hook state * 2. 回调执行后更新 fiber 数,让 schdule 更新数 * @param {any} initialState * @returns */ const useState = (initialState) => { const oldHook = wipFiber.alternate && wipFiber.alternate.hooks && wipFiber.alternate.hooks[hookIndex]; const hook = { state: oldHook ? oldHook.state : initialState, queue: [] }; const actions = oldHook ? oldHook.queue : []; actions.forEach((action) => { hook.state = action(hook.state); }); const setState = (actionCallback) => { const action = typeof actionCallback !== 'function' ? () => actionCallback : actionCallback; 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]; };
把数据保存在 hook 上,上屏操作时把 DOM 指向 fiber.props.ref.current 上
const useRef = (initialValue) => { const oldHook = wipFiber?.alternate?.hooks?.[hookIndex]; const ref = { current: initialValue }; const hook = { state: oldHook?.state || ref }; wipFiber.hooks.push(hook); hookIndex ++; return ref; }; const updateHostComponent = (fiber) => { if (!fiber.dom) { fiber.dom = createDOM(fiber); if (fiber?.props?.ref) { fiber.props.ref.current = fiber.dom; } } reconcileChildren(fiber, fiber.props.children); };
两个参数,第一个是回调,第二是监听的数据,关键点:
function useEffect(effect, deps) { const oldHook = wipFiber?.alternate?.hooks?.[hookIndex]; const hasChanged = hasDepsChanged(oldHook?.deps ?? undefined, deps); const hook = { tag: "effect", effect: hasChanged ? effect : null, cancel: hasChanged && oldHook?.cancel, deps, }; wipFiber.hooks.push(hook); hookIndex++; } // commitWork 阶段需要做DOM的回调 ...... if (fiber.effectTag === "PLACEMENT") { if (dom != null) { domParent.appendChild(dom); } runEffects(fiber); } else if (fiber.effectTag === "UPDATE") { cancelEffects(fiber); if (fiber.dom !== null) { updateDom(dom, fiber.alternate.props, fiber.props); } runEffects(fiber); } else if (fiber.effectTag === "DELETION") { cancelEffects(fiber); commitDeletion(fiber, domParent); return; } ......