2025-05-02 日报 Day174

2025-05-02 日报 Day174

Yuyang 前端小白🥬

今日的鸡汤

真正的勤勉,从来不是盲目地忙,而是时时有所创造、事事有所成就、处处有所精进。

今日学习内容

1、https://pomb.us/build-your-own-react/

今日笔记

1、Step Three: Concurrent Mode
But… before we start adding more code we need a refactor.

There’s a problem with this recursive call.

Once we start rendering, we won’t stop until we have rendered the complete element tree. If the element tree is big, it may block the main thread for too long.

And if the browser needs to do high priority stuff like handling user input or keeping an animation smooth, it will have to wait until the render finishes.

So we are going to break the work into small units, and after we finish each unit we’ll let the browser interrupt the rendering if there’s anything else that needs to be done.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let nextUnitOfWork = null

function workLoop(deadline){
let shouldYield = false
while(nextUnitOfWork && !shouldYield){
nextUnitOfWork = performUnitOfWork(
nextUnitOfWork
)
shouldYield = deadline.timeRemaining() < 1
}
requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

function performUnitOfWork(nextUnitOfWork){
// TODO

}

React doesn’t use requestIdleCallback anymore. Now it uses the scheduler package. But for this use case it’s conceptually the same.

2、Step Four: Fibers
To organize the units of work we’ll need a data structure: a fiber tree.

We’ll have one fiber for each element and each fiber will be a unit of work.

Let me show you with an example.

Suppose we want to render an element tree like this one:

1
2
3
4
5
6
7
8
9
10
React.render(
<div>
<h1>
<p />
<a />
</h1>
<h2 />
</div>,
container
)

In the render we’ll create the root fiber and set it as the nextUnitOfWork. The rest of the work will happen on the performUnitOfWork function, there we will do three things for each fiber:

1、add the element to the DOM
2、create the fibers for the element’s children
3、select the next unit of work

One of the goals of this data structure is to make it easy to find the next unit of work. That’s why each fiber has a link to its first child, its next sibling and its parent.

image-20250621135701965

When we finish performing work on a fiber, if it has a child that fiber will be the next unit of work.

From our example, when we finish working on the div fiber the next unit of work will be the h1 fiber.

If the fiber doesn’t have a child, we use the sibling as the next unit of work.

For example, the p fiber doesn’t have a child so we move to the a fiber after finishing it.

And if the fiber doesn’t have a child nor a sibling we go to the “uncle”: the sibling of the parent. Like a and h2 fibers from the example.

Also, if the parent doesn’t have a sibling, we keep going up through the parents until we find one with a sibling or until we reach the root. If we have reached the root, it means we have finished performing all the work for this 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
73
74
75
76
77
78
function render(element, container){
const dom =
element.type == "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type)

const isProperty = key => key !== "children";
Object.keys(element.props)
.filter(isProperty)
.forEach(name => {
dom[name] = element.props[name]
})

element.props.children.forEach(child =>
render(child, dom)
)
container.appendChild(dom)
}

let nextUnitOfWork = null

function workLoop(deadline){
let shouldYield = false
while(nextUnitOfWork && !shouldYield){
nextUnitOfWork = performUnitOfWork(
nextUnitOfWork
)
shouldYield = deadline.timeRemaining() < 1
}
requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

function performUnitOfWork(nextUnitOfWork){
// TODO add dom node
if(!fiber.dom){
fiber.dom = createDom(fiber)
}

if(fiber.parent){
fiber.parent.dom.appendChild(fiber.dom)
}
// TODO create new fibers
const elements = fiber.props.children
let index = 0
let preSibling = null
while(index < elements.length){
const element = elements[index]

const newFiber = {
type: element.type,
props: element.props,
parent: fiber,
dom: null,
}
if(index === 0){
fiber.child = newFiber // first child
} else {
preSibling.sibling = newFiber // next sibling
}
preSibling = newFiber // update previous sibling
index++
}
// TODO return next unit of work

}

if(fiber.child){
return fiber.child // next unit of work is the first child
}
let nextFiber = fiber
while(nextFiber){
if(nextFiber.sibling){
return nextFiber.sibling // next unit of work is the sibling
}
nextFiber = nextFiber.parent // go up to the parent
}
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

function performUnitOfWork(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}

if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom)
}

const elements = fiber.props.children
let index = 0
let prevSibling = null

while (index < elements.length) {
const element = elements[index]

const newFiber = {
type: element.type,
props: element.props,
parent: fiber,
dom: null,
}

if (index === 0) {
fiber.child = newFiber
} else {
prevSibling.sibling = newFiber
}

prevSibling = newFiber
index++
}

if (fiber.child) {
return fiber.child
}
let nextFiber = fiber
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling
}
nextFiber = nextFiber.parent
}
}

3、Step Five: Render and Commit Phases
We are adding a new node to the DOM each time we work on an element. And, remember, the browser could interrupt our work before we finish rendering the whole tree. In that case, the user will see an incomplete UI. And we don’t want that.

Instead, we’ll keep track of the root of the fiber tree. We call it the work in progress root or wipRoot.

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 commitRoot() {
// TODO add nodes to dom
commitWork(wipRoot.child)
wipRoot = null // reset the wipRoot
}
function commitWork(fiber){
if(!fiber) {
return
}
const domParent = fiber.parent.dom
domParent.appendChild(fiber.dom)
commitWork(fiber.child) // commit the child
commitWork(fiber.sibling) // commit the sibling
}
function render(element, container){
wipRoot = {
dom: container,
props: {
children: [element],
},
}
nextUnitOfWork = wipRoot
}

let nextUnitOfWork = null
let wipRoot = null
function workLoop(deadline) {
let shouldYield = false
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
shouldYield = deadline.timeRemaining() < 1
}
if(!nextUnitOfWork && wipRoot) {
commitRoot()
}
requestIdleCallback(workLoop)
}

And once we finish all the work (we know it because there isn’t a next unit of work) we commit the whole fiber tree to the DOM.
4、Step Six: Reconciliation

So far we only added stuff to the DOM, but what about updating or deleting nodes?
That’s what we are going to do now, we need to compare the elements we receive on the render function to the last fiber tree we committed to the DOM.
So we need to save a reference to that “last fiber tree we committed to the DOM” after we finish the commit. We call it currentRoot.
We also add the alternate property to every fiber. This property is a link to the old fiber, the fiber that we committed to the DOM in the previous commit phase.

function performUnitOfWork(fiber){
    if(!fiber.dom){
        fiber.dom = createDom(fiber)
    }

    const element = fiber.props.children
    let index = 0
    let preSibling = null

    while(index < elements.length){
        const element = elements[index]

        const newFiber = {
            type: element.type,
            props: element.props,
            parent: fiber,
            dom: null
        }
        if(index === 0){
            fiber.child = newFiber // first child
        } else {
            preSibling.sibling = newFiber // next sibling
        }
        preSibling = newFiber // update previous sibling
        index++
    }

    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 preSibling = null

    while(
        index < elements.length ||
        oldFiber !== null
    ) {
        const element = elements[index]
        let newFiber = null

        // Compare oldFiber to element
        const sameType = oldFiber && element && oldFiber.type === element.type

        if(sameType){
            // Update the old fiber
            newFiber = {
                type: oldFiber.type,
                props: element.props,
                dom: oldFiber.dom,
                parent: wipFiber,
                alternate: oldFiber, // link to the old fiber
            }
        }

        if(element && !sameType){
            // Add a new fiber
            newFiber = {
                type: element.type,
                props: element.props,
                dom: null,
                parent: wipFiber,
            }
        }

        if(oldFiber && !sameType){
            // Delete the old fiber
            oldFiber.effectTag = "DELETION"
        }

        if(oldFiber) {
            oldFiber = oldFiber.sibling // move to the next sibling
        }

        if(index === 0){
            wipFiber.child = newFiber // first child
        } else {
            preSibling.sibling = newFiber // next sibling
        }
        preSibling = newFiber // update previous sibling
        index++
    }
}
此页目录
2025-05-02 日报 Day174