《react源码学习系列》-函数式组件更新过程

56 阅读2分钟

示例代码

import React, { useState } from 'react'
import logo from './logo.svg'
import './App.css'

const ChildCom = () => {
  return <ul style={{height: '40px', overflow: 'auto', border: '1px solid red'}}>
    <li>1</li>
    <li>2</li>
    <li>{new Date().getTime()}</li>
    <p>4</p>
  </ul>
}
function App() {
  let [count, setCount] = useState(0)

  const ChildCom2 = () => {
    return <ul style={{height: '40px', overflow: 'auto', border: '1px solid red'}}>
      <li>1</li>
      <li>2</li>
      <li>{new Date().getTime()}</li>
      <li>4</li>
    </ul>
  }
  return (
    <div>
     
      <ChildCom2/>

 {/* 先插入这里,在对比,这时候比较的是两个ul */}
      {ChildCom2()}

      {/* <ChildCom/> */}
       <button onClick={() => setCount(count+1)}>change count</button>
    </div>
  )
}


ReactDOM.render(
    <App/> ,
  document.getElementById('root')
)

babel转译之后

import React, { useState } from "react";
import logo from "./logo.svg";
import "./App.css";

const ChildCom = () => {
  return /*#__PURE__*/ React.createElement(
    "ul",
    {
      style: {
        height: "40px",
        overflow: "auto",
        border: "1px solid red"
      }
    },
    /*#__PURE__*/ React.createElement("li", null, "1"),
    /*#__PURE__*/ React.createElement("li", null, "2"),
    /*#__PURE__*/ React.createElement("li", null, new Date().getTime()),
    /*#__PURE__*/ React.createElement("p", null, "4")
  );
};

function App() {
  let [count, setCount] = useState(0);

  const ChildCom2 = () => {
    return /*#__PURE__*/ React.createElement(
      "ul",
      {
        style: {
          height: "40px",
          overflow: "auto",
          border: "1px solid red"
        }
      },
      /*#__PURE__*/ React.createElement("li", null, "1"),
      /*#__PURE__*/ React.createElement("li", null, "2"),
      /*#__PURE__*/ React.createElement("li", null, new Date().getTime()),
      /*#__PURE__*/ React.createElement("li", null, "4")
    );
  };

  return /*#__PURE__*/ React.createElement(
    "div",
    null,
    /*#__PURE__*/ React.createElement(ChildCom2, null),
    ChildCom2(),
    /*#__PURE__*/ React.createElement(
      "button",
      {
        onClick: () => setCount(count + 1)
      },
      "change count"
    )
  );
}

ReactDOM.render(
  /*#__PURE__*/ React.createElement(App, null),
  document.getElementById("root")
);

我们从div这一层级开始遍历

workLoopSync

performUnitOfWork

beginWork

function beginWork(current, workInProgress, renderLanes) {

	switch (workInProgress.tag) {
    ...
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    ...
  }

}

updateHostComponent

这里nextChildren拿到的是当前fiber对应的虚拟dom中的子元素虚拟dom,结构如下

这里传到后面的nextChildren中的每一项相当于是newFiber

function updateHostComponent(current, workInProgress, renderLanes) {
  ...
  var nextProps = workInProgress.pendingProps;
  var nextChildren = nextProps.children;
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

reconcileChildren

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  if (current === null) {
    // If this is a fresh new component that hasn't been rendered yet, we
    // won't update its child set by applying minimal side-effects. Instead,
    // we will add them all to the child before it gets rendered. That means
    // we can optimize this reconciliation pass by not tracking side-effects.
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
    // If the current child is the same as the work in progress, it means that
    // we haven't yet started any work on these children. Therefore, we use
    // the clone algorithm to create a copy of all the current children.
    // If we had any progressed work already, that is invalid at this point so
    // let's throw it out.
    workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
  }
}

reconcileChildFibers

对fiber节点处理,下面说的主要涉及下面几个函数

var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);


function ChildReconciler(shouldTrackSideEffects) {
  function deleteChild(returnFiber, childToDelete) {}
	function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, lanes) {}
  function updateSlot(returnFiber, oldFiber, newChild, lanes) {}
  function updateElement(returnFiber, current, element, lanes) {}
  ...其它
  function reconcileChildFibers(){}
	return reconcileChildFibers
}

reconcileChildrenArray

updateSlot生成新的newFiber

deleteChild删除了oldFiber

placeChild对新的fiber做标记

function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, lanes) {
	var oldFiber = currentFirstChild;
	for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
      
      var newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], lanes);

      if (shouldTrackSideEffects) {
        if (oldFiber && newFiber.alternate === null) {
          // We matched the slot, but we didn't reuse the existing fiber, so we
          // need to delete the existing child.
          deleteChild(returnFiber, oldFiber);
        }
      }

      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);

}

updateSlot

newChildren中的每一项和oldFiber做对比,看是否需要重新生成Fiber

function updateSlot(returnFiber, oldFiber, newChild, lanes) {
    var key = oldFiber !== null ? oldFiber.key : null;

    if (typeof newChild === 'string' || typeof newChild === 'number') {
      if (key !== null) {
        return null;
      }

      return updateTextNode(returnFiber, oldFiber, '' + newChild, lanes);
    }

    if (typeof newChild === 'object' && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          {
            if (newChild.key === key) {

              return updateElement(returnFiber, oldFiber, newChild, lanes);
            } else {
              return null;
            }
          }

      }
    }

    return null;
  }

updateElement

决定是生成新的fiber还是复用旧的fiber

function updateElement(returnFiber, current, element, lanes) {
    if (current !== null) {
      if (current.elementType === element.type || ( // Keep this check inline so it only runs on the false path:
       isCompatibleFamilyForHotReloading(current, element) )) {
        // Move based on index
        var existing = useFiber(current, element.props);
        existing.ref = coerceRef(returnFiber, current, element);
        existing.return = returnFiber;

        {
          existing._debugSource = element._source;
          existing._debugOwner = element._owner;
        }

        return existing;
      }
    } // Insert


    var created = createFiberFromElement(element, returnFiber.mode, lanes);
    created.ref = coerceRef(returnFiber, current, element);
    created.return = returnFiber;
    return created;
  }

这时候current.elementType === element.type不相等,引用已经不同。

走了下面的新建Fiber createFiberFromElement, 返回的这个created结构如下,alternate是没有值,因为是新建的

deleteChild

从上面可知alternate为null,则reconcileChildrenArray中会走这个函数里

给fiber添加删除flags标记

function deleteChild(returnFiber, childToDelete) {
    if (!shouldTrackSideEffects) {
      // Noop.
      return;
    } // Deletions are added in reversed order so we add it to the front.
    // At this point, the return fiber's effect list is empty except for
    // deletions, so we can just append the deletion to the list. The remaining
    // effects aren't added until the complete phase. Once we implement
    // resuming, this may not be true.


    var last = returnFiber.lastEffect;

    if (last !== null) {
      last.nextEffect = childToDelete;
      returnFiber.lastEffect = childToDelete;
    } else {
      returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
    }

    childToDelete.nextEffect = null;
    childToDelete.flags = Deletion;
  }

placeChild

此时newFiber.flags = Placement,该节点被标记了替换

function placeChild(newFiber, lastPlacedIndex, newIndex) {
    newFiber.index = newIndex;

    if (!shouldTrackSideEffects) {
      // Noop.
      return lastPlacedIndex;
    }

    var current = newFiber.alternate;

    if (current !== null) {
      var oldIndex = current.index;

      if (oldIndex < lastPlacedIndex) {
        // This is a move.
        newFiber.flags = Placement;
        return lastPlacedIndex;
      } else {
        // This item can stay in place.
        return oldIndex;
      }
    } else {
      // This is an insertion.
      newFiber.flags = Placement;
      return lastPlacedIndex;
    }
  }

子元素递归

接下来进行下一轮递归

performUnitOfWork

var current = unitOfWork.alternate;这里有个赋值操作,

这里的unitOfWork就是 workInProgress

就是上一轮递归的next = beginWork$1(current, unitOfWork, subtreeRenderLanes);

beginWork$1的返回值就是上一轮的updateElement的返回值新建的Fiber,是没有alternate引用的

function performUnitOfWork(unitOfWork) {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  var current = unitOfWork.alternate;
  setCurrentFiber(unitOfWork);
  var next;

  if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
  }

  resetCurrentFiber();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner$2.current = null;
}

beginWork

这里的workInProgress.tag是2,因为从上面可知,workInProgress 这是新建的Fiber对象,tag是默认2

function beginWork(current, workInProgress, renderLanes) {
  
	switch (workInProgress.tag) {
    case IndeterminateComponent:
      {
        return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
      }
  }

}

mountIndeterminateComponent

function mountIndeterminateComponent(_current, workInProgress, Component, renderLanes) {
		reconcileChildren(null, workInProgress, value, renderLanes);


    return workInProgress.child;
}

reconcileChildren

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  if (current === null) {
    // If this is a fresh new component that hasn't been rendered yet, we
    // won't update its child set by applying minimal side-effects. Instead,
    // we will add them all to the child before it gets rendered. That means
    // we can optimize this reconciliation pass by not tracking side-effects.
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
    // If the current child is the same as the work in progress, it means that
    // we haven't yet started any work on these children. Therefore, we use
    // the clone algorithm to create a copy of all the current children.
    // If we had any progressed work already, that is invalid at this point so
    // let's throw it out.
    workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
  }
}

mountChildFibers

var mountChildFibers = ChildReconciler(false);
ChildReconciler () {
  reconcileChildFibers(){}
	return reconcileChildFibers
}

mountChildFibers === reconcileChildFibers

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
  
  
    var isObject = typeof newChild === 'object' && newChild !== null;

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

        case REACT_PORTAL_TYPE:
          return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes));

      }
    }

  }

reconcileSingleElement

currentFirstChild从上面传下来的是null

function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
    var key = element.key;
    var child = currentFirstChild;

    while (child !== null) {
      // TODO: If key === null and child.key === null, then this only applies to
      // the first item in the list.
      if (child.key === key) {
        switch (child.tag) {}
    }

    if (element.type === REACT_FRAGMENT_TYPE) {
      var created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key);
      created.return = returnFiber;
      return created;
    } else {
      var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);

      _created4.ref = coerceRef(returnFiber, currentFirstChild, element);
      _created4.return = returnFiber;
      return _created4;
    }
  }

所以这里又新建了ul标签的Fiber节点