inpm
! dlrow olleH

React16-fiber

2018-12-31 React
Word count: 977 | Reading time: 5min

fiber是什么?

  • React16中对diff算法的重构
  • 由stack架构(递归调用阻塞线程)转为fiber架构

为什么fiber?

  • 横向对比stack架构
  • 调度微任务,利用空闲期执行回调
  • 使diff算法可控可追踪(达到非阻塞目的)

怎样实现fiber?

  • 纤维化(纤维队列)
    我们需要将工作分成更小的部分,并使之能在短时间内运行,让主线程做更高优先级的事情
    如果线程空闲,那么回到纤维队列

  • fiber对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let fiber = {
    tag: HOST_ROOT||HOST_COMPONENT||CLASS_COMPONENT,
    type: "div"||Foo,
    parent: parentFiber,
    child: childFiber,
    sibling: null,
    alternate: currentFiber,
    stateNode: document.createElement("div"), // instance
    props: { children: [], className: "foo"},
    partialState: null,
    effectTag: PLACEMENT,
    effects: []
    };
  • window.requestIdleCallback()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    const ENOUGH_TIME = 1 //毫秒ms
    let workQueue = [] // 纤维队列
    let nextUnitOfWork = null

    function schedule(task) {
    workQueue.push(task)
    requestIdleCallback(performWork)
    }

    function performWork(deadline) {
    if(!nextUnitOfWork) {
    nextUnitOfWork = workQueue.shift()
    }
    while(nextUnitOfWork && deadline.timeRemaining()>ENOUGH_TIME) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork) // 执行当前work并返回下次work的所有信息
    }
    if(nextUnitOfWork || workQueue.length > 0) {
    requestIdleCallback(performWork)
    }
    if(pendingCommit) {
    commitAllWork(pendingCommit);
    }
    }
  • 纤维队列

    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
    // Fiber tags
    const HOST_COMPONENT = "host"
    const CLASS_COMPONENT = "class"
    const HOST_ROOT = "root"

    // Global state
    const updateQueue = [] // 存储等待更新
    let nextUnitOfWork = null
    let pendingCommit = null

    function render(elements, containerDom) {
    updateQueue.push({
    from: HOST_ROOT,
    dom: containerDom,
    newProps: { children: elements }
    })
    requestIdleCallback(performWork)
    }

    function scheduleUpdate(instance, partialState) {
    updateQueue.push({
    from: CLASS_COMPONENT,
    instance: instance,
    partialState: partialState
    })
    requestIdleCallback(performWork)
    }

    提问root节点作为第一帧如何转换成nextUnitOfWork
    答案:

    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
    function resetNextUnitOfWork() {
    const update = updateQueue.shift();
    if (!update) {
    return
    }

    // 将setState参数从更新有效内容复制到相应的光纤
    if (update.partialState) {
    update.instance.__fiber.partialState = update.partialState
    }

    const root =
    update.from == HOST_ROOT
    ? update.dom._rootContainerFiber
    : getRoot(update.instance.__fiber)

    nextUnitOfWork = {
    tag: HOST_ROOT,
    stateNode: update.dom || root.stateNode,
    props: update.newProps || root.props,
    alternate: root
    }
    }

    function getRoot(fiber) {
    let node = fiber
    while (node.parent) {
    node = node.parent
    }
    return node
    }

    提问如何构造后续帧并生成fiberTree🌲?
    答案:
    先处理叶子节点,然后是其兄弟节点,然后是双亲节点,从下往上遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function performUnitOfWork(wipFiber) {
    beginWork(wipFiber)
    if (wipFiber.child) {
    return wipFiber.child
    }

    // 遍历,直到没有child
    let uow = wipFiber
    while (uow) {
    completeWork(uow)
    if (uow.sibling) {
    // Sibling needs to beginWork
    return uow.sibling
    }
    uow = uow.parent
    }
    }
  • fiber流程图
    fiber流程图

  • 处理更新组件props和state

    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
    function beginWork(wipFiber) {
    if (wipFiber.tag == CLASS_COMPONENT) {
    updateClassComponent(wipFiber);
    } else {
    updateHostComponent(wipFiber);
    }
    }

    function updateHostComponent(wipFiber) {
    if (!wipFiber.stateNode) {
    wipFiber.stateNode = createDomElement(wipFiber);
    }
    const newChildElements = wipFiber.props.children;
    reconcileChildrenArray(wipFiber, newChildElements);
    }

    function updateClassComponent(wipFiber) {
    let instance = wipFiber.stateNode;
    if (instance == null) {
    // Call class constructor
    instance = wipFiber.stateNode = createInstance(wipFiber);
    } else if (wipFiber.props == instance.props && !wipFiber.partialState) {
    // No need to render, clone children from last time
    cloneChildFibers(wipFiber);
    return;
    }

    instance.props = wipFiber.props;
    instance.state = Object.assign({}, instance.state, wipFiber.partialState);
    wipFiber.partialState = null;

    const newChildElements = wipFiber.stateNode.render();
    reconcileChildrenArray(wipFiber, newChildElements);
    }
  • 开始diff,将oldFiber与elements做对比

    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
    // Effect tags
    const PLACEMENT = 1;
    const DELETION = 2;
    const UPDATE = 3;

    function arrify(val) {
    return val == null ? [] : Array.isArray(val) ? val : [val];
    }

    function reconcileChildrenArray(wipFiber, newChildElements) {
    const elements = arrify(newChildElements);

    let index = 0;
    let oldFiber = wipFiber.alternate ? wipFiber.alternate.child : null;
    let newFiber = null;
    while (index < elements.length || oldFiber != null) {
    const prevFiber = newFiber;
    const element = index < elements.length && elements[index];
    const sameType = oldFiber && element && element.type == oldFiber.type;

    if (sameType) {
    newFiber = {
    type: oldFiber.type,
    tag: oldFiber.tag,
    stateNode: oldFiber.stateNode,
    props: element.props,
    parent: wipFiber,
    alternate: oldFiber,
    partialState: oldFiber.partialState,
    effectTag: UPDATE
    };
    }

    if (element && !sameType) {
    newFiber = {
    type: element.type,
    tag:
    typeof element.type === "string" ? HOST_COMPONENT : CLASS_COMPONENT,
    props: element.props,
    parent: wipFiber,
    effectTag: PLACEMENT
    };
    }

    if (oldFiber && !sameType) {
    oldFiber.effectTag = DELETION;
    wipFiber.effects = wipFiber.effects || [];
    wipFiber.effects.push(oldFiber);
    }

    if (oldFiber) {
    oldFiber = oldFiber.sibling;
    }

    if (index == 0) {
    wipFiber.child = newFiber;
    } else if (prevFiber && element) {
    prevFiber.sibling = newFiber;
    }

    index++;
    }
    }
  • 最后一步,更新到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
    42
    43
    44
    45
    46
    function commitAllWork(fiber) {
    fiber.effects.forEach(f => {
    commitWork(f);
    });
    fiber.stateNode._rootContainerFiber = fiber;
    nextUnitOfWork = null;
    pendingCommit = null;
    }

    function commitWork(fiber) {
    if (fiber.tag == HOST_ROOT) {
    return;
    }

    let domParentFiber = fiber.parent;
    while (domParentFiber.tag == CLASS_COMPONENT) {
    domParentFiber = domParentFiber.parent;
    }
    const domParent = domParentFiber.stateNode;

    if (fiber.effectTag == PLACEMENT && fiber.tag == HOST_COMPONENT) {
    domParent.appendChild(fiber.stateNode);
    } else if (fiber.effectTag == UPDATE) {
    updateDomProperties(fiber.stateNode, fiber.alternate.props, fiber.props);
    } else if (fiber.effectTag == DELETION) {
    commitDeletion(fiber, domParent);
    }
    }

    function commitDeletion(fiber, domParent) {
    let node = fiber;
    while (true) {
    if (node.tag == CLASS_COMPONENT) {
    node = node.child;
    continue;
    }
    domParent.removeChild(node.stateNode);
    while (node != fiber && !node.sibling) {
    node = node.parent;
    }
    if (node == fiber) {
    return;
    }
    node = node.sibling;
    }
    }

Author: inpm.cy@gmail.com

Link: https://inpm.top/2018/12/31/React16-fiber/

Copyright: All articles in this blog are licensed under inpm unless stating additionally.

< PreviousPost
【#2019面试准备】知识点梳理
NextPost >
手写一个精简版React
CATALOG
  1. 1. fiber是什么?
  2. 2. 为什么fiber?
  3. 3. 怎样实现fiber?