【React源码学习】6 commitMutationEffects的调用过程


demo

更新DOM

export default function App() {
    const [c, setC] = useState(0)

    return (
        <div onClick={() =>{
            setC(c + 1)
        }}>
            {c}
        </div>

    );
}

commitMutationEffects

commitMutationEffects阶段主要进行DOM更新操作,下面看一下过程

if (flags & Ref) {
    var current = nextEffect.alternate;

    if (current !== null) {
        commitDetachRef(current);
    }
}

这一部分主要进行解绑ref的操作,因为dom可能发生变化,在改变dom前通过commitDetachRef进行解绑。

var primaryFlags = flags & (Placement | Update | Deletion | Hydrating);

这一段获取flag,demo没有涉及插入删除操作,因此获取到的flag是Update,然后进行更新操作

commitWork

case Update:
{
    var _current3 = nextEffect.alternate;
    commitWork(_current3, nextEffect);
    break;
}

从switch case进入更新函数,调用commitWork,在commitWork内根据tag进入下面的逻辑

case HostComponent:
{
    var instance = finishedWork.stateNode;
    if (instance != null) {
        var newProps = finishedWork.memoizedProps; 
        var oldProps = current !== null ? current.memoizedProps : newProps;
        var type = finishedWork.type; // TODO: Type the updateQueue to be specific to host components.

        var updatePayload = finishedWork.updateQueue;
        finishedWork.updateQueue = null;

        if (updatePayload !== null) {
            commitUpdate(instance, updatePayload, type, oldProps, newProps);
          }
    }

    return;
}

finishedWork.stateNode就是在completeWork阶段生成的DOM对象,此时引用的未更新的DOM。获取updateQueue后调用commitUpdate

commitUpdate

function commitUpdate(domElement, updatePayload, type, oldProps, newProps, internalInstanceHandle) {
    updateFiberProps(domElement, newProps); 
    updateProperties(domElement, updatePayload, type, oldProps, newProps);
}

updateFiberProps将newProps挂载到dom的一个特殊id上(这个属性应该是根据某种算法生成的)

updateProperties调用updateDOMProperties

for (var i = 0; i < updatePayload.length; i += 2) {
    var propKey = updatePayload[i];
    var propValue = updatePayload[i + 1];

    if (propKey === STYLE) {
      setValueForStyles(domElement, propValue);
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      setInnerHTML(domElement, propValue);
    } else if (propKey === CHILDREN) {
      setTextContent(domElement, propValue);
    } else {
      setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
    }
}

updateDOMProperties内遍历updateQueue,根据属性更新DOM。

下面换个demo,看一下插入DOM的逻辑

export default function App() {
    const [c, setC] = useState(0)

    const a = <div key="a">a</div>;
    const b = <div key="b">b</div>;

    return (
        <div onClick={() =>{
            setC(c + 1)
        }}>
            {c % 2 === 1 ? a : b}
        </div>

    );
}

触发更新后进入下面的部分

case Deletion:
{
    commitDeletion(root, nextEffect);
    break;
}

effectList的第一个fiber是workInProgress.alternate(先被打上deletion的fiber,下一个就是placement的fiber),然后调用相应的unmount函数,根据parent的情况调用removeChild,移除DOM节点

function removeChild(parentInstance, child) {
    parentInstance.removeChild(child);
}

(其实到这个地方才知道effectList为啥要包含current fiber树上即将被删除的fiber,因为react内是删除DOM和添加DOM的操作,而不是替换,所以current fiber树和workInProgress fiber树上有变化的fiber都要加入到effectList)

看一下第二个effect的插入逻辑

case Placement:
{
    commitPlacement(nextEffect); 
    nextEffect.flags &= ~Placement;
    break;
}

在commitPlacement内调用下面的代码

if (isContainer) {
    insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
} else {
    insertOrAppendPlacementNode(finishedWork, before, parent);
}

根据parent与node的关系调用不同的插入函数


Author: Maple
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Maple !
  TOC