统一提交
问题:页面显示不全
浏览器繁忙,requestIdleCallback不够时间render 完整个vdom,这时浏览器会渲染已完成的部分dom,导致页面显示不全
解决
等render完了再统一提交
- 将原来的fiber.parent.append(dom) 改为
commitWork
和commitRoot
- 在processUnitOfWork 之后判断是否执行
commitRoot
,仅当nextWork = null 时执行(已经处理完所有fiber链表节点,没有剩余的时候)let root = null
- render 函数中初始设置 root = 根节点
commitRoot
中append 到root节点并设置root = null
支持函数组件写法
第一步:支持App和其子组件Counter的function 写法
1.1 写App function返回dom
javascript
import React from "./core/React.js";
function Counter() {
return (
<div>
<div>count</div>
</div>
)
}
// const App = <div id="app">hi-mini-react</div>;
function App() {
return <div>
hi-mini-react
<Counter/>
</div>;
}
1.2 解决报错
【分析】
- 使用function 返回dom,这时打印发现在正常处理vdom过程中遇到了function
- 在performUnitofWork 函数中debug可以看到 typeof fiber.type === "function"。它无法直接用来做
document.createElement
或者document.createTextNode
【解决】
- 我们要做的就是执行这个function拿到vdom
- 函数组件就是一个盒子,包裹着vdom;执行这个函数就是开盒子,拿到vdom
【代码逻辑】
- 跳过 createElement 和 updateProps - 函数组件本身不会成为一个实际的 html element
- 执行function并将其返回值作为children处理
javascript
function performWorkOfUnit(fiber) {
// 如果是函数组件,不直接为其append dom
const isFunctionComponent = typeof fiber.type === "function";
if (!isFunctionComponent) {
if (!fiber.dom) {
const dom = (fiber.dom = createDom(fiber.type));
updateProps(dom, fiber.props);
}
}
// 将函数组件的返回值塞进数组,因为children是一个数组
const children = isFunctionComponent ? [fiber.type()] : fiber.props.children
initChildren(fiber, children)
...
仍然显示异常
【原因】vdom中函数组件占据一个节点。但是map到dom结构中它是null
【解决】
javascript
function commitWork(fiber) {
if (!fiber) return
// 原来的逻辑:导致报错 ->
// App函数组件的 fiber.parent.dom = null;
// App的Counter子组件 fiber.dom = null
// fiber.parent.dom.append(fiber.dom)
// 新逻辑:
// 跳过函数组件造成的vdom -> dom结构断层
let fiberParent = fiber.parent;
while (!fiberParent.dom) {
fiberParent = fiberParent.parent
}
// vdom中的函数组件节点本身并没有对应的dom节点,append 会导致null被append到dom树上
if (fiber.dom) {
fiberParent.dom.append(fiber.dom)
}
commitWork(fiber.child)
commitWork(fiber.sibling)
}
1.3 成功展示函数组件App和Counter
第二步:支持函数组件的props
2.1 添加属性并传值
添加完发现打印出来的props是undefined
js
function Counter(props) {
console.log("props: ", props);
return (
<div>
<div>count: {props.num}</div>
</div>
)
}
function App(params) {
return (
<div>
mini-react
<Counter num={10}></Counter>
</div>
)
}
这就引导我们去调用 Counter
函数的地方去了
js
const children = isFunctionComponent ? [fiber.type()] : fiber.props.children
这里没有传入参数。添加debugger查看fiber object的结构,发现props就在 fiber.props
上。回想最初建立vdom结构时,<div id="app"></div>
中的id属性其实也是直接在fiber.props
上的。
将[fiber.type()]
改为[fiber.type(fiber.props)]
就可以看到Counter函数中正确打印值了
2.2 处理属性值
此时页面仍然报错
通过查看当前节点的 parent.props.children
发现当前节点就是传入的数字10
。
而实际上数字10 应当像它前面的count:
一样在dom中建成一个textNode
css
<div>count: {props.num}</div>
这就需要修改 createElement
函数了: 当child是数字的时候,也调用 createTextNode
。
js
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) => {
// const isTextNode = typeof child === "string"
const isTextNode = typeof child === "string" || typeof child === "number"
return isTextNode ? createTextNode(child) : child
}),
},
};
}
【成果】 现在属性值被正确render了。
第三步:支持多个函数组件
加多一个Counter组件发现第二个没被render。
【原因】 现有代码只会找叔叔的sibling,当叔叔没有sibling的时候还应该找爷爷的sibling (以及更高层级的,如果有的话)
js
// 4. 返回下一个要执行的任务
if (fiber.child) {
return fiber.child;
}
if (fiber.sibling) {
return fiber.sibling;
}
return fiber.parent?.sibling;
【解决】
js
// 4. 返回下一个要执行的任务
if (fiber.child) {
return fiber.child;
}
// 循环找parent和它的sibling
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
// 如果有sibling就返回
return nextFiber.sibling;
} else {
// 否则就往上找
nextFiber = nextFiber.parent;
}
}
【成果】
能正常显示了
阶段总结
总体而言,由于函数组件dom=null导致了我们在commitWork中append dom的时候需要向上查找parent;以及在遍历fiber链表结构的时候需要处理没有叔叔的情况。
重构代码
- 将普通组件和函数组件分成两个函数来处理,减少各处的
isFunctionComponent
分支。
js
function updateFunctionComponent(fiber) {
// 如果是函数组件,不直接为其append dom
const children = [fiber.type(fiber.props)]
initChildren(fiber, children)
}
function updateHostComponent(fiber) {
// 将vdom节点转换为dom节点
// 如果vdom节点对应的dom节点未被创建,先创建dom节点,并append到父节点的dom上
if (!fiber.dom) {
const dom = (fiber.dom = createDom(fiber.type));
updateProps(dom, fiber.props);
}
const children = fiber.props.children;
initChildren(fiber, children);
}