嗯,文章很水,还没彻底理解。可以搜索 mini react 相关文案,会有更好的学习分享。
需求:简单实现 react 的部分基础功能。
注意: 本文章 在 学校 催学社: 《mini-react 游戏副本》后,输出。
课程学习链接:
https://learn.cuixueshe.com/p/t_pc/course_pc_detail/camp_pro/course_2aWyh6wtEv0gud4X9CjSKHgSQwz
一共7天的课程。将mini-react 的功能拆分,每日完成一部分。这样的学习方式 初次体验,感觉还不错。
在本次课程中学到了 :
- 任务如何拆分(小步快走)。
- 在基础方法实现后,如何去重构方法。
3.如何将树 结构 转换为链表结构。
4.requestIdleCallback (https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback)
注意:由于requestIdleCallback利用的是帧的空闲时间,所以就有可能出现浏览器一直处于繁忙状态。
实现方法:
update,
render,
createElement,
useState,
useEffect,
主要代码:
react.js
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) => {
const isTextNode =
typeof child === "string" || typeof child === "number";
return isTextNode ? createTextNode(child) : child;
}),
},
};
}
function render(el, container) {
wipRoot = {
dom: container,
props: {
children: [el],
},
};
nextWorkOfUnit = wipRoot;
}
let wipRoot = null;
let nextWorkOfUnit = null;
let currentRoot = null;
let deletions = [];
let wipFiber = null;
function workLoop(deadline) {
let shouldYield = false;
while (!shouldYield && nextWorkOfUnit) {
nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit);
if (wipRoot?.sibling?.type === nextWorkOfUnit?.type) {
// console.log("hot", wipRoot, nextWorkOfUnit);
nextWorkOfUnit = undefined;
}
shouldYield = deadline.timeRemaining() < 1;
}
if (!nextWorkOfUnit && wipRoot) {
commitRoot();
}
requestIdleCallback(workLoop);
}
function commitRoot() {
deletions.forEach(commitDeletion);
commitWork(wipRoot.child);
commitEffectHooks();
currentRoot = wipRoot;
wipRoot = null;
deletions = [];
}
function commitEffectHooks() {
function run(fiber) {
if (!fiber) {
return;
}
if (!fiber.alternate) {
// init
fiber.effectHooks?.forEach((hook) => {
hook.cleanup = hook.callback();
});
} else {
// update
// deps 是否更新
fiber.effectHooks?.forEach((newHook, index) => {
if (newHook.deps.length > 0) {
const oldEffectHook = fiber.alternate?.effectHooks[index];
const needUpdate = oldEffectHook?.deps.some((oldDep, i) => {
return oldDep !== newHook.deps[i];
});
needUpdate && (newHook.cleanup = newHook.callback());
}
});
}
run(fiber.child);
run(fiber.sibling);
}
function runCleanup(fiber) {
if (!fiber) {
return;
}
fiber.alternate?.effectHooks?.forEach((hook) => {
if (hook.deps.length > 0) {
hook.cleanup && hook.cleanup();
}
});
runCleanup(fiber.child);
runCleanup(fiber.sibling);
}
runCleanup(wipRoot);
run(wipRoot);
}
function commitDeletion(fiber) {
if (fiber.dom) {
let fiberParent = fiber.parent;
while (!fiberParent.dom) {
fiberParent = fiberParent.parent;
}
fiberParent.dom.removeChild(fiber.dom);
} else {
commitDeletion(fiber.child);
}
}
function commitWork(fiber) {
if (!fiber) return;
let fiberParent = fiber.parent;
while (!fiberParent.dom) {
fiberParent = fiberParent.parent;
}
if (fiber.effectTag === "update") {
updateProps(fiber.dom, fiber.props, fiber.alternate?.props);
} else if (fiber.effectTag === "placement") {
if (fiber.dom) {
fiberParent.dom.append(fiber.dom);
}
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
function createDom(type) {
return type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(type);
}
function updateProps(dom, nextProps, pervProps) {
// old 有 new 没有 删除
Object.keys(pervProps).forEach((key) => {
if (key !== "children") {
if (!(key in nextProps)) {
dom.removeAttribute(key);
}
}
});
// new 有 old 没有添加
// new 有 old 有 修改
Object.keys(nextProps).forEach((key) => {
if (key !== "children") {
if (nextProps[key] !== pervProps[key]) {
if (key.startsWith("on")) {
const eventType = key.slice(2).toLowerCase();
dom.removeEventListener(eventType, pervProps[key]);
dom.addEventListener(eventType, nextProps[key]);
} else {
dom[key] = nextProps[key];
}
}
}
});
}
function reconcileChildren(fiber, children) {
let oldFiber = fiber.alternate?.child;
let prevChild = null;
children.forEach((child, index) => {
const isSameType = oldFiber && oldFiber.type === child.type;
let newFiber;
if (isSameType) {
newFiber = {
type: child.type,
props: child.props,
child: null,
parent: fiber,
sibling: null,
dom: oldFiber.dom,
effectTag: "update",
alternate: oldFiber,
};
} else {
if (child) {
newFiber = {
type: child.type,
props: child.props,
child: null,
parent: fiber,
sibling: null,
dom: null,
effectTag: "placement",
};
}
if (oldFiber) {
console.log("should delete", oldFiber);
deletions.push(oldFiber);
}
}
if (oldFiber) {
oldFiber = oldFiber.sibling;
}
if (index === 0) {
fiber.child = newFiber;
} else {
prevChild.sibling = newFiber;
}
if (newFiber) {
prevChild = newFiber;
}
});
while (oldFiber) {
deletions.push(oldFiber);
oldFiber = oldFiber.sibling;
}
}
function updateFunctionComponent(fiber) {
stateHooks = [];
effectHooks = [];
stateHookIndex = 0;
wipFiber = fiber;
const children = [fiber.type(fiber.props)];
// 3. 转化链表 设置指针
reconcileChildren(fiber, children);
}
function updateHostComponent(fiber) {
if (!fiber.dom) {
// 1.创建dom
const dom = (fiber.dom = createDom(fiber.type));
// 2.处理props
updateProps(dom, fiber.props, {});
}
const children = fiber.props.children;
// 3. 转化链表 设置指针
reconcileChildren(fiber, children);
}
function performWorkOfUnit(fiber) {
const isFunctionComponent = typeof fiber.type === "function";
if (isFunctionComponent) {
updateFunctionComponent(fiber);
} else {
updateHostComponent(fiber);
}
// 4. 返回要执行的下一个任务
if (fiber.child) {
return fiber.child;
}
// if (fiber.sibling) {
// return fiber.sibling;
// }
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) return nextFiber.sibling;
nextFiber = nextFiber.parent;
}
// return fiber.parent?.sibling;
}
requestIdleCallback(workLoop);
function update() {
let currentFiber = wipFiber;
return () => {
wipRoot = {
...currentFiber,
alternate: currentFiber,
};
nextWorkOfUnit = wipRoot;
};
}
let stateHooks;
let stateHookIndex;
function useState(initial) {
let currentFiber = wipFiber;
const oldHook = currentFiber.alternate?.stateHooks[stateHookIndex];
const stateHook = {
state: oldHook ? oldHook.state : initial,
queue: oldHook ? oldHook.queue : [],
};
stateHook.queue.forEach((action) => {
stateHook.state = action(stateHook.state);
});
stateHook.queue = [];
stateHookIndex++;
stateHooks.push(stateHook);
currentFiber.stateHooks = stateHooks;
function setState(action) {
const eagerState =
typeof action === "function" ? action(stateHook.state) : action;
if (eagerState === stateHook.state) {
return;
}
stateHook.queue.push(typeof action === "function" ? action : () => action);
wipRoot = {
...currentFiber,
alternate: currentFiber,
};
nextWorkOfUnit = wipRoot;
}
return [stateHook.state, setState];
}
let effectHooks;
function useEffect(callback, deps) {
const effectHook = {
callback,
deps,
cleanup: undefined,
};
effectHooks.push(effectHook);
wipFiber.effectHooks = effectHooks;
}
const React = {
update,
render,
createElement,
useState,
useEffect,
};
export default React;
ReactDom.js
import React from "./React.js";
const ReactDOM = {
createRoot(container) {
return {
render(App) {
React.render(App, container);
},
};
},
};
export default ReactDOM;
app.jsx
import React from "./core/React.js";
let countFoo = 1;
function Foo() {
console.log("re foo");
const [count, setCount] = React.useState(10);
const [bar, setBar] = React.useState("bar");
function handleClick() {
setCount((c) => c + 1);
setBar(() => "bar");
}
React.useEffect(() => {
console.log("init");
return () => {
console.log("cleanup 0");
};
}, []);
React.useEffect(() => {
console.log("update", count);
return () => {
console.log("cleanup 1");
};
}, [count]);
React.useEffect(() => {
console.log("update", count);
return () => {
console.log("cleanup 2");
};
}, [count]);
return (
<div>
<h1>foo</h1>
{count}
<div>{bar}</div>
<button onClick={handleClick}>click</button>
</div>
);
}
function App() {
return (
<div>
hi-mini-react
<Foo></Foo>
</div>
);
}
export default App;