【React源码学习】4 Diff算法 单节点Diff


本次实验的demo如下

export default function App() {
    const [toggle, setToggle] = useState(true);

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

    return (
        <div
            onClick={() => {
                setToggle(!toggle);
            }}
        >
            {toggle ? a : b}
        </div>
    );
}

performUnitOfWork

第一次节点切换时先看一下performUnitOfWork

var current = unitOfWork.alternate;

current指向的是workInProgress的同层fiber,react的diff是基于同层fiber的对比

performUnitOfWork内调用了beginWork,看一下beginWork的参数。

此时current.pendingProps.children === current.memoizedProps.children

{
    $$typeof: Symbol(react.element)
    key: "a"
    props: {children: "a"}
    ref: null
    type: "div"
}

workInProgress的memoizedProps.children

{
    $$typeof: Symbol(react.element)
    key: "a"
    props: {children: "a"}
    ref: null
    type: "div"
}

而pendingProps.children变成了

{
    $$typeof: Symbol(react.element)
    key: "b"
    props: {children: "b"}
    ref: null
    type: "div"
}

也就是说jsx对象的变化先出现在了workInProgress的pendingProps上,下面对比oldProps和newProps

var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;

if (oldProps !== newProps){
    didReceiveUpdate = true;    //ture
}

根据workInProgress的tag调用updateHostComponent

updateHostComponent

var nextProps = workInProgress.pendingProps;
var prevProps = current !== null ? current.memoizedProps : null;
var nextChildren = nextProps.children;

// some code

reconcileChildren(current, workInProgress, nextChildren, renderLanes);

updateHostComponet获取pedingProps和memoizedProps然后调用reconcileChildren

reconcileChildren

此时收到的三个参数

  1. current是workInProgress.alternate,详见performUnitOfWork
  2. workInProgress,当前的核心feiber
  3. nextChildren是workInProgress的pendingProps,新的jsx体现在该属性上

reconcileChildren内部调用reconcileChildFibers, 该函数和mountChildFibers都是childReconciler的返回值

workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);

reconcileChildFibers

前三个参数:

  1. workInProgress fiber
  2. workInProgress.alternate.child
  3. workInProgress.pendingProps.children

reconcileChildFibers内调用reconcileSingleElement(单节点diff)和placeSingleChild

if (isObject) {
    switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
            return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
        case REACT_PORTAL_TYPE:
            // some code
    }
}

reconcileSingleElement(单节点diff)

reconcileSIngleElement根据child != null进入update还是mount, 此时进行update

while (child !== null) {
  if (child.key === key) {
    switch (child.tag) {
      case Fragment:
        {
            // some code
        }
      case Block:
      default:
        {
          if (child.elementType === element.type || (isCompatibleFamilyForHotReloading(child, element) )) {
            // some code
  } else {
    deleteChild(returnFiber, child);
  }

child.key === ‘a’ 而 element.key === b,节点无法复用,直接删除,复用的逻辑以后会补充

deleteChild(returnFiber, child);
        |
        |
       \|/
childToDelete.flags = Deletion;

先不考虑effect的逻辑,当fiber无法复用时给current.child打上Deletion flag

由于fiber无法复用,因此创建新的fiber

var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);
_created4.ref = coerceRef(returnFiber, currentFirstChild, element);
_created4.return = returnFiber;
return _created4;

placeSingleChild

if (shouldTrackSideEffects && newFiber.alternate === null) {
    newFiber.flags = Placement;
}

Placement是代表插入的flag,给新的fiber当上Placement

其实fiber对比值得就是current.child fiber与新的jsx(workInProgress.pendingProps.children)之间的对比,无法复用就给current.child打上Deletion flag,同时根据新的jsx创建新的fiber挂载到workInProgress.child上,同时打上Placement flag。

summary

总结一下reconcile相关函数的调用关系

reconcileRelation

single Diff的工作流程

process


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