React源码学习记录

751 阅读15分钟

react 采用的是双缓存结构所以应用中是有两颗渲染树的,正在被作用于视图的fiber树这里成为current 树,而另一颗称为workInporgress  树,首次渲染称为Mount时期,此时current 为空, 之后称为Update时期

Fiber  数据结构

function FiberNode(tag, pendingProps, key, mode) {  
// Instance 
 this.tag = tag; //Fiber节点对应实例的类型,  
this.key = key; 
 this.elementType = null; //hook和class 就是对应的函数,和class类,dom 对应的就是就是dom标签名  
this.type = null;  
this.stateNode = null; // Fiber  
this.return = null;  //指向 父节点 
 this.child = null;   //指向 孩子节点 
 this.sibling = null; //指向 兄弟节点  
this.index = 0;  
this.ref = null;

  this.pendingProps = pendingProps; //当前workInprogress Fiber需要更新的props  
this.memoizedProps = null;    //上一次渲染的props  
this.updateQueue = null;       //如果是class component ,保存setState 产生的更新链表。如果是hook,存放副作用链表  
this.memoizedState = null; //如果是hook 则保存的是所有hook组成的环转链表 如果是class组件保存的是上一次的state 
 this.dependencies = null;  this.mode = mode; // Effects

//副作用标记 eg:Placement -->  插入 ,Update-->更新,Deletion --->  删除  
this.flags = NoFlags;    
this.lanes = NoLanes;  //优先级相关的
 this.alternate = null;  //双存储树指向另一个树中对应的fiber节点}



function App() {
  const [num, setNum] = useState(1);
  return (
    <div className="App">
      <div className='container'>
        <div className='imgBox'>
          <img></img>
        </div>
        <p className='text'>hello</p>
      </div>
      <button onClick={() => setNum(num + 1)}>{num}</button>
    </div>
  );}

Fiber中的flags  属性重点说一下,这个属性表示了当前的fiber节点拥有那些副作用,他是一个复数形式说明能不值一个副作用,其中比较常见的比如Placement ---》代表插入操作、Update -->代表将执行更新操作,而很奇怪的是PlacementAndUpdate = Placement | Update ,在源码中对副作用fiber.flags 的操作中大量运用到了按位或 运算,可以理解为,一个fiber 可能拥有多个副作用,通过按位或 方法将多个 副作用合并,例如PlacementAndUpdate代表既有插入操作也有更新操作 而源码中最终的表现形式就是 PlacementAndUpdate = Placement | Update

var NoFlags =/*                      */0;
var PerformedWork =/*                */1; // You can change the rest (and add more).
var Placement =/*                    */2;
var Update =/*                       */4;
var PlacementAndUpdate =/*           */Placement | Update;var ChildDeletion =/*                */16;
var ContentReset =/*                 */32;
var Callback =/*                     */64;
var DidCapture =/*                   */128;
var ForceClientRender =/*            */256;
var Ref =/*                          */512;
var Snapshot =/*                     */1024;
var Passive =/*                      */2048;
var Hydrating =/*                    */4096;
var HydratingAndUpdate =/*           */Hydrating | Update;
var Visibility =/*                   */8192;
var StoreConsistency =/*             */16384;
var LifecycleEffectMask = Passive | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)var HostEffectMask =/*               */32767; // These are not really side effects, but we still reuse this field.
var Incomplete =/*                   */32768;
/.
.
.
./more

React 的diff过程

react 的diff过程实际上是通过 深度优先搜索 算法 通过递归对fiber节点遍历的过程,整个递归过程又可以分为 递 阶段和 归阶段 。由于对于整个应用生命周期而言,首次渲染是只存在一个current 渲染树 称为Mount 阶段,之后称为Update 阶段,此时存在 workInprogress  和 current 两颗树,后期整个更新的过程 实际上是 通过diff 时基于 current fiber 生成 对应的workInprogress fiber ,两者通过 alternate 属性 形成映射。最后 FiberRootNode  根结点指向新生成 workinprogress 渲染树,此时workInprogress  渲染树 就成为了 current 渲染树。

前文提到过递归可分为递阶段 和 归 阶段,递阶段 开始于 begainWork() ,归阶段 开始于 completeWork(),接下来看看 递阶段 和 归阶段 具体都干了什么事。

前面说过 react采用的是 深度优先搜索 实现遍历渲染树,当某一个fiber节点没有孩子节点的时候它就会进入归阶段,入口就在 completeWork

function App() {  
const [num, setNum] = useState(1);  
return ( 
    <div className="App">     
    <div className='container'>        
    <div className='imgBox'>          
        <img></img>        
    </div>        
    <p className='text'>hello</p>      
</div>     
 <button onClick={() => setNum(num + 1)}>{num}</button>    
   </div>  );}

以上面的结构为例整个的递归顺序如下

  1. div.App --->进入 beginWork

  2. div.container ---> 进入beginWork

  3. div.imgBox ---> 进入 beginWork

  4. img. --->进入begin work

  5. img --->进入 completeWork (此时img下没有孩子节点了)

  6. div.imgBox ---> 进入 completeWork (因为 img 没有兄弟节点所以直接进入 img 父节点的completeWork)

  7. p.text. ---> beginWork(div.imgBox completeWork 结束后发现有sibling 兄弟节点,所以执行兄弟节点的 beginWork)

  8. p.text ---> completeWork( 这里 可能会有疑问 ,为啥 p.text 下的文本标签不执行 beginWork ,直接进入p 标签 的completeWork 了,原因在于之前曾说过,react 对单文本节点 做了优化,是不会产生新的fiber节点的,具体可在 reconcileSingleTextNode () 函数中查看)

  9. div.container ----> 进入completeWork

  10. button ---> beginWork (button 是 div.container 的兄弟节点)

  11. button ---> completeWork (child 是 singleTextNode 单文本节点)

  12. div.App ---> completeWork

beginWork  递阶段

fiber的tag属性 表示Fiber节点对应实例的类型,在beginWork函数中会通过tag 的类型进入不同的update逻辑 比如 函数组件 tag = 0 对应FunctionComponent,类组件tag = 1对应 ClassComponent,而 普通的dom 元素的tag = 5 对应 HostComponent ,在首次渲染的时候也就是Mount时,函数组件和类组件tag会被赋值为2对应 IndeterminateComponent 意思为不确

定的组件 ,它会调用mountIndeterminateComponent函数对其进行判断然后给予相应的类型。

以下为 所有tag 对映的类型

var FunctionComponent = 0;var ClassComponent = 1;var IndeterminateComponent = 2; // Before we know whether it is function or classvar HostRoot = 3; // Root of a host tree. Could be nested inside another node.var HostPortal = 4; // A subtree. Could be an entry point to a different renderer.var HostComponent = 5;var HostText = 6;var Fragment = 7;var Mode = 8;var ContextConsumer = 9;var ContextProvider = 10;var ForwardRef = 11;var Profiler = 12;var SuspenseComponent = 13;var MemoComponent = 14;var SimpleMemoComponent = 15;var LazyComponent = 16;var IncompleteClassComponent = 17;var DehydratedFragment = 18;var SuspenseListComponent = 19;var ScopeComponent = 21;var OffscreenComponent = 22;var LegacyHiddenComponent = 23;var CacheComponent = 24;

function beginWork(current, workInProgress, renderLanes) {

  if (current !== null) {
    var oldProps = current.memoizedProps;
    var newProps = workInProgress.pendingProps;

    //todo....
  } else {
    didReceiveUpdate = false;

    if (getIsHydrating() && isForkedChild(workInProgress)) {
     
      var slotIndex = workInProgress.index;
      var numberOfForks = getForksAtLevel();
      pushTreeId(workInProgress, numberOfForks, slotIndex);
    }
  } 

    //初始化优先级
  workInProgress.lanes = NoLanes;
//通过不同的tag 进行不同的更新
  switch (workInProgress.tag) {
    case IndeterminateComponent: //初始化 整个应用首次渲染是Function Component和Class Component 走的逻辑
      {
        return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
      }
    case FunctionComponent: //函数组件
      {
        var Component = workInProgress.type;
        var unresolvedProps = workInProgress.pendingProps;
        var resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps);
        return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes);
      }
    case ClassComponent://  类组件
      {
        //.......
        return updateClassComponent(current, workInProgress, _Component, _resolvedProps, renderLanes);
      }

    case HostRoot: //FiberRootNode 所走的更新逻辑
      return updateHostRoot(current, workInProgress, renderLanes);

    case HostComponent: //普通dom元素走的逻辑
      return updateHostComponent$1(current, workInProgress, renderLanes);

    case HostText:
      return updateHostText$1(current, workInProgress);

    //......more
  }
}


在源码中如何区分Mount时期还是Update时期。beginWork(current, workInProgress, renderLanes)在于current 等不等于null,如果为null

则为mount时期,不为null 则是update时期,但有个例外,在整个应用初始化的时候current和workInProgress都不为null,这个current 代表的是整个应用的根结点 FiberRootNode

后面我就用FiberRootNode 表示根节点,一般为应用挂在的dom元素 <div id="root"></div>

而FiberRootNode 对应的tag 为HostRoot

在beginWork对应的更新逻辑中,都会调用 reconcileChildred(current,workInProgress,nextChildren,renderLanes)方法,这里面看着是调用了两个方法 实际上 mountChildFibers()和 reconcileChildFibers() 调用的是同一个方法ChildReconciler()方法,区别在于 传的参数的不同, 参数为true时 代表需要跟踪副作用标记 false 不需要。

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {  
//mount 和 update 实际调用同一个函数  
// var reconcileChildFibers = ChildReconciler(true);  
// var mountChildFibers = ChildReconciler(false);  
//差别在于 参数为true是 代表需要跟踪副作用标记 false 不需要  
if (current === null) {    
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);  
    } else {
    workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes); 
 }}

ChildReconciler() 方法实际上是返回一个 基于闭包 返回 reconcileChildFibers()方法,所以实际上调用的是 reconcileChildFibers 方法,其中通过 fiber.$$typeof 属性去判断 走具体的reconcile 逻辑,如果newChild为单节点 执行reconcileSingleElement(),newChild为数组即workInprogressFiber 有多个孩子节点,则执行reconcileChildrenArray()

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
    //判断是不是Fragment标签    
var isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null;    
//如果是Fragment 标签则忽略他,更行newChild为Fragment 的children    
if (isUnkeyedTopLevelFragment) {      
newChild = newChild.props.children;   
 } // Handle object types    
//$$typeof 走不通的协调逻辑 单节点 执行reconcileSingleElement ,多节点 执行reconcileChildrenArray    
//如果是单个的 文本节点的话 走reconcileSingleTextNode ,这里对单文本节点做了优化,如果为单文本节点不会生成Fiber    
if (typeof newChild === 'object' && newChild !== null) { 
     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));
        case REACT_LAZY_TYPE:
          {
            var payload = newChild._payload;
            var init = newChild._init; // TODO: This function is supposed to be non-recursive. 
            return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes);
          }
      }
      if (isArray(newChild)) {
        //多节点 最终形成的一条通过sibling指针连接接的一条链表返回第一个头节点
        return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes);
      }
    if (typeof newChild === 'string' && newChild !== '' || typeof newChild === 'number') { 
     //单文本节点 //不会生产Fiber节点
      return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, lanes));
    }
    {
      if (typeof newChild === 'function') {
        warnOnFunctionType(returnFiber);
      }
    } // Remaining cases are all treated as empty.
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

以 reconcileSingleElement 为例,真正的diff 比较逻辑 就在这,先比较key , 如果key相同在比较类型 ,如果key  不同则比较 类型,其实还有其他的一些逻辑比较 比如 位置的变化等等

function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
    var key = element.key;    var child = currentFirstChild;
    //diff逻辑 
   while (child !== null) {
            //比较Key
      if (child.key === key) {
        var elementType = element.type;
        if (elementType === REACT_FRAGMENT_TYPE) {
          if (child.tag === Fragment) {
            deleteRemainingChildren(returnFiber, child.sibling);
            var existing = useFiber(child, element.props.children);
            existing.return = returnFiber;
            {
              existing._debugSource = element._source;
              existing._debugOwner = element._owner;
            }
            return existing;
          }
        } else {
          if (child.elementType === elementType || ( // Keep this check inline so it only runs on the false path:
           isCompatibleFamilyForHotReloading(child, element) ) || // Lazy types should reconcile their resolved type.
            typeof elementType === 'object' && elementType !== null && elementType.$$typeof === REACT_LAZY_TYPE && resolveLazy(elementType) === child.type) {
            deleteRemainingChildren(returnFiber, child.sibling);
            var _existing = useFiber(child, element.props);
            _existing.ref = coerceRef(returnFiber, child, element);
            _existing.return = returnFiber;
            {
              _existing._debugSource = element._source;
              _existing._debugOwner = element._owner;
            }
            return _existing;
          }
        } // Didn't match.
        deleteRemainingChildren(returnFiber, child);
        break;
      } else {
        deleteChild(returnFiber, child);
      }
      child = child.sibling;
    }    //创建fiber 并返回
    if (element.type === REACT_FRAGMENT_TYPE) {
      var created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key);
      created.return = returnFiber;
      return created;
    } else {
      //调用 createFiberFromElement -> createFiberFromTypeAndProps ->       var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);
      _created4.ref = coerceRef(returnFiber, currentFirstChild, element);
      _created4.return = returnFiber;
      return _created4;
    }
  }

最后 创建fiber 并返回。

总结:beginWork 其实就是 根据tag 类型 进入不同的更新逻辑,在不同的update中 其实有共同点就是,先调用reconcileChildren ,然后返回 workInprogress fiber 的第一个孩子,重点是第一个孩子,而reconcileChildren  Mount 和 Update 时期都是调用ChildReconciler(shouldTrackSideEffects:boolean),ChildReconciler利用闭包 返回一个名为reconcileChildFibers的函数,这里面通过 判断 newChild.$$typeof 或者 isArray(newChild)或者是否为单文本节点来执行 对应的reconcile 逻辑,所以真正的 比较过程 是发生在这里的。

所以递阶段 beginWork 就是返回 当前workInprogress fiber  的第一个 孩子节点。

completeWork 归阶段

completeWork  具体干了啥,对于 普通的 html dom 元素 ,fiber.tag ==5 == HostComponent 

function completeWork(current, workInProgress, renderLanes) {  
    var newProps = workInProgress.pendingProps; // Note: This intentionally doesn't check if we're hydrating because comparing  popTreeContext(workInProgress);  switch (workInProgress.tag) {    //...    case HostComponent:      {        popHostContext(workInProgress);        var rootContainerInstance = getRootHostContainer();//获取跟节点实例 div.root        var type = workInProgress.type; //html 标签名        //Update 时期        if (current !== null && workInProgress.stateNode != null) {
    updateHostComponent(current, workInProgress, type, newProps, rootContainerInstance);
          if (current.ref !== workInProgress.ref) {
            markRef(workInProgress);
          }
        } else { 
         //Mount时期
          var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
             var _wasHydrated = popHydrationState(workInProgress);
          if (_wasHydrated) {//ssr 的不用管
            if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
              markUpdate(workInProgress);
            }
          } else {
            //实际上关键代码
            var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
            appendAllChildren(instance, workInProgress, false, false);
            workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount.
          }
        }
        bubbleProperties(workInProgress);
        return null;
      }
  throw new Error("Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in " + 'React. Please file an issue.');
}

对于HostComponent 普通的html 标签而言,在completeWork 中如果为Mount时期主要就是调用createInstance()   方法

function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
  var parentNamespace;
 //......
  var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
  precacheFiberNode(internalInstanceHandle, domElement);
  updateFiberProps(domElement, props);  return domElement;
}

在 createInstance () 方法中主要就是 调用createElement()方法创建domElement并返回 ,实际在内部是通过docuemtn.createElement()方法去创建 dom元素

function createElement(type, props, rootContainerElement, parentNamespace) {
  var isCustomComponentTag; // We create tags in the namespace of their parent container, except HTML    //拿到当前的 document 对象  var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerElement);  var domElement;  var namespaceURI = parentNamespace;

  if (namespaceURI === HTML_NAMESPACE) {
    //创建script 标签    if (type === 'script') {
      var div = ownerDocument.createElement('div');
      div.innerHTML = '<script><' + '/script>';
 // eslint-disable-line
      var firstChild = div.firstChild;
      //删除外层的div标签 保存 script 标签
      domElement = div.removeChild(firstChild);
    } else if (typeof props.is === 'string') {
      // $FlowIssue `createElement` should be updated for Web Components
      domElement = ownerDocument.createElement(type, {
        is: props.is
      });
    } else {     //根据标签名 创建 dom元素 在web中实际上就是 document.createElement()
      domElement = ownerDocument.createElement(type); // Normally attributes are assigned in `setInitialDOMProperties`, however the `multiple` and `size`
         if (type === 'select') {
        var node = domElement;
        if (props.multiple) {
          node.multiple = true;
        } else if (props.size) {
               node.size = props.size;
        }
      }
    }
  } else {
    domElement = ownerDocument.createElementNS(namespaceURI, type);
  }
  {
    if (namespaceURI === HTML_NAMESPACE) {
      if (!isCustomComponentTag && Object.prototype.toString.call(domElement) === '[object HTMLUnknownElement]' && !hasOwnProperty.call(warnedUnknownTags, type)) {
        warnedUnknownTags[type] = true;
        error('The tag <%s> is unrecognized in this browser. ' + 'If you meant to render a React component, start its name with ' + 'an uppercase letter.', type);
      }
    }
  }
  return domElement;
}

总结:completeWork 对于 HostComponent Mount时期根据fiber 信息 ,调用document.createElement方法创建dom的过程,Update 主要调用UpdateHostComponent 方法进行更新。

Hook

对于函数组件而言,整个的调用顺序为 beginWork----> 判断tag--->调用 updateFunctionComponent --->renderWithHooks,所以组件真正的调用发生在renderWithHooks 中,

function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
  renderLanes = nextRenderLanes;  //记录当前正在执行的Fiber节点
  currentlyRenderingFiber$1 = workInProgress;

  //初始化
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes; // The following should have already been reset
  {    //判断dispatcher 的指向,是执行mount的方法还是update的方法
    if (current !== null && current.memoizedState !== null) {
      ReactCurrentDispatcher$1.current = HooksDispatcherOnUpdateInDEV;
    } else if (hookTypesDev !== null) {
      ReactCurrentDispatcher$1.current = HooksDispatcherOnMountWithHookTypesInDEV;
    } else {
      ReactCurrentDispatcher$1.current = HooksDispatcherOnMountInDEV;
    }
  }  //render 执行构造函数,Component实际上就是 我们的函数组件方法
  var children = Component(props, secondArg); // Check if there was a render phase update
  //didScheduleRenderPhaseUpdateDuringThisPass  全局变量 判断是否需要 从新render。因为在执行构造函数的时候 只要使用了setstate就会产生 re-render
  if (didScheduleRenderPhaseUpdateDuringThisPass) {
    //进行 re-render 逻辑
    var numberOfReRenders = 0;
    do {
      didScheduleRenderPhaseUpdateDuringThisPass = false;
      localIdCounter = 0;
      numberOfReRenders += 1;//记录reRender次数
      currentHook = null;
      workInProgressHook = null;
      workInProgress.updateQueue = null;
      ReactCurrentDispatcher$1.current =  HooksDispatcherOnRerenderInDEV ;
      children = Component(props, secondArg);
    } while (didScheduleRenderPhaseUpdateDuringThisPass);
  }   //执行完后关闭hooks的调用接口
  ReactCurrentDispatcher$1.current = ContextOnlyDispatcher;
    //....
  return children;
}

例如这个组件

function Apple() {
  const [num, setNum] = useState(1);
  const [name, setName] = useState("");
  const [age, setAge] = useState(23);
  const handleAdd = () => {
    setNum("德玛西亚");
    setNum("洛克萨斯");
    setAge(age + 1);
  };
//这段代码就会引起re-render ,他在组件render的过程中调用了set方法
  if (num == 1) {
    setNum(num + 1);
  }


  useEffect(() => {
    setName("yangxiang-杨祥");
  }, []);
  return (
    <div className="apple">
      <p>
        Apple {num},age{age}
      </p>
      <button onClick={() => handleAdd()}>add Apple</button>
    </div>
  )
;}

上述renderWithHooks 中的 Component 调用就是 Apple 方法的调用,所以的 hooks调用 也都发生在这Apple函数里,至于之前在renderWithHook中讲到的 didScheduleRenderPhaseUpdateDuringThisPass。产生re-render 的情况,就如上面Apple 组建里面的 if(num==1){setNum(num+1)} 函数组件执行的同时调用 set 方法出发更新 就会产生 re-render , 不用 整棵树从新渲染。

首先在学习hook源码之前先了解一下hook这一块相关的数据结构。

 var hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
  };

对于不同的hook 里面所存放的东西各不相同,比如useState 产生的hook,它的memoizedState保存的就是就是你返回的state,比如useEffect,保存的就是一条effect 链表,后面具体细说。

useState/useEffect产生的hook

 var hook = {
    memoizedState: null,  //保存最新的状态
    baseState: null,   //保存状态
    baseQueue: null,   //更新中断未更新的update 链表
    queue: null,       //queue.pending 指向update环状链表的最后一个update
    next: null         //下一个hook
  };

useEffect/useLayoutEffect产生的hook

var hook = {
    memoizedState: null,  //保存effect 链表
    baseState: null,   
    baseQueue: null,   
    queue: null,       
    next: null         //下一个hook
  };

useRef

var hook = {
    memoizedState: null,  //{current:XXX}的对象
    baseState: null,   
    baseQueue: null,   
    queue: null,       
    next: null         //下一个hook
  };

useCallback

var hook = {
    memoizedState: null,  //保存一个数组 [callback,deps],第零位  传入的方法,第一位为依赖项
    baseState: null,   
    baseQueue: null,   
    queue: null,       
    next: null         //下一个hook
  };

useMemo

var hook = {
    memoizedState: null,  //保存一个数组[nextvalue,nextDeps], 一个是值,一个是依赖项
    baseState: null,   
    baseQueue: null,   
    queue: null,       
    next: null         //下一个hook
  };

useState

之前在renderWithHook()方法中曾经提到过,ReactCurrentDispatcher$1.current

所指向的就是我们要调用的hook方法,每一个官方提供的hook 方法在mount 和 update 的不同时期都会调用不同的方法,比如useState 对映有mountState,updateState两个方法,首先这里说明一下这里的mount和update是相对于hook方法的生命周期而言,而之前在讲react的mount和update是指的是应用的生命周期而言。

首先来介绍一下useState,中涉及到的数据结构update,每次调用useState 返回的dispatch函数,也就是 返回数组的第二个,都会生成一个update

 var update = {
    lane: lane, //优先级
    action: action, //可以是一个值,可以是一个函数,比如下方第一个setNum产生的update中action = 1,
                    //第二个action = ()=> 'yangxiang '    
hasEagerState: false,
    eagerState: null,
    next: null    //指向下一个update的指针
  };
cosnt [num,setNum] = useState()
cosnt [name,setName] = useState()

我们首先来理一下 hook 数据结构,update 数据节后 ,和fiber 之前的关系

fiber对映了一个工作单元,比如我们的组件,原生dom元素等等,hook 是我们在function Component 中执行官方提供的hook方法,比如执行一个useState()就会产生一个对应的hook,而useState提供了一个我们用于改变状态的set方法比如上面的setNum、setName,每执行一次set方法就会产生一个update.

下面为几个数据结构的关系

fiber----包含---> hook -----包含--->update

mountState

function mountState(initialState) {
  //初始化hook 
 var hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  //保存状态 
 hook.memoizedState = hook.baseState = initialState;
  var queue = {
    pending: null,//一个指向由update组成的环转链表最后一个update的指针
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState
  };
  hook.queue = queue;
  var dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber$1, queue);
  return [hook.memoizedState, dispatch];
}
  1. 初始化hook
  2. 处理initialState 为函数的情况
  3. 保存初始化状态到hook.memoizedState和hook.baseState中
  4. 生成一个queue ,queue中最重要的作用之一就是通过queue.pending 指向update环转链表的最后一个update
  5. 生成每一个useState对应的dispatch函数,也就是我们用来改变状态的那个set方法
  6. 最后返回当前的状态和dispatch方法

我们来看看mountWorkInProgressHook都做了什么

function mountWorkInProgressHook() {
  var hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null, //
    queue: null,
    next: null
  };
  if (workInProgressHook === null) {
    // This is the first hook in the list
    currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

其实很简单,

  1. 创建一个hook 数据结构,
  2. workInProgressHook 即为当前正在执行中的hook,如果 workInProgressHook === null ,则吧创建的hook 赋值给workInProgressHook,并且挂载到当前正在渲染的fiber.memoizedState上,否则就放到链表的最后一个
  3. 最后返回创建的hook

而另一个需要注意的就是产生用于更新的dispatch函数的方法:dispatchSetState

function dispatchSetState(fiber, queue, action) {
  {
    if (typeof arguments[3] === 'function') {
      error("State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + 'rendering, declare it in the component body with useEffect().');
    }
  }
  var lane = requestUpdateLane(fiber);
  var update = {
    lane: lane,
    action: action,
    hasEagerState: false,
    eagerState: null,
    next: null
  };
  if (isRenderPhaseUpdate(fiber)) {
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    enqueueUpdate$1(fiber, queue, update);
    var alternate = fiber.alternate;
    if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
       var lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        var prevDispatcher;
        {
          prevDispatcher = ReactCurrentDispatcher$1.current;
          ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
        }
        try {
          var currentState = queue.lastRenderedState;
          var eagerState = lastRenderedReducer(currentState, action); // Stash the eagerly computed state, and the reducer used to compute
                    update.hasEagerState = true;
          update.eagerState = eagerState;
          if (objectIs(eagerState, currentState)) { 
           return;
          }
        } catch (error) {// Suppress the error. It will throw again in the render phase.
        } finally {
          {
            ReactCurrentDispatcher$1.current = prevDispatcher;
          }
        }
      }
    }
    var eventTime = requestEventTime();
    var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    if (root !== null) {
      entangleTransitionUpdate(root, queue, lane);
    }
  }
  markUpdateInDevTools(fiber, lane);
}

上述为完整代码实际上的核心代码在这里

function dispatchSetState(fiber, queue, action) {
    var update = {
    lane: lane,
    action: action,
    hasEagerState: false,
    eagerState: null,
    next: null
  };
    //是否在渲染阶段更新 
 if (isRenderPhaseUpdate(fiber)) {
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    enqueueUpdate$1(fiber, queue, update);
    var alternate = fiber.alternate;
       var eventTime = requestEventTime();
    var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
      }
  markUpdateInDevTools(fiber, lane);
}
  1. 每次调用修改状态的set方法生成一个update数据结构
  2. 判断是否在渲染阶段更新
  3. 调用scheduleUpdateOnFiber ,执行调度方法,实现更新

其中主要的代码在enqueueRenderPhaseUpdate()和enqueueUpdate()中,具体来看看做了啥

enqueueRenderPhaseUpdate

function enqueueRenderPhaseUpdate(queue, update) {
   didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  var pending = queue.pending;
  if (pending === null) {
    // This is the first update. Create a circular list.
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
}
  1. 进入这个方法说明是在渲染阶段进行了更新, didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true ,触发 renderWithHook 方法里面曾讲到的re-render逻辑
  2. 判断 hook.queue.pending 是否为空,如果为空则初始化换装链表,不为空则将生成的update放到链表的最后一个
  3. 把hook.queue.pending 指向最后一个update,对应了之前讲的

enqueueUpdate

function enqueueUpdate$1(fiber, queue, update, lane) {
  //是否是多根节点交错更新的情况,非常少见
  if (isInterleavedUpdate(fiber)) {
    var interleaved = queue.interleaved;
    if (interleaved === null) {
      // This is the first update. Create a circular list.
      update.next = update; // At the end of the current render, this queue's interleaved updates will
      // be transferred to the pending queue.
      pushInterleavedQueue(queue);
    } else {
      update.next = interleaved.next;
      interleaved.next = update;
    }
    queue.interleaved = update;
  } else {
    var pending = queue.pending;
    if (pending === null) {
      // This is the first update. Create a circular list.
      //第一个update,生成环状链表
      update.next = update;
    } else {
      update.next = pending.next;
      pending.next = update; 
   }
    queue.pending = update;
  }
}
  1. 以判断是否是多根节点交错更新的情况,非常少见(没实际调试过)
  2. 核心代码同样是判断pending是否为空,如果等于null则 生成 update的环转链表,否则插入到链表的末尾,最后通过queue.pending 指向最后一个

到此为止mountState部分学习到此为止了

updateState

在hook的update时期调用的就是updateState方法,也就是我们调用对应改变state的dispatch函数的时候,比如 [num,setNum] = useState(1) ,执行setNum(2) 方法时就会执行当前hook方法的update方法

function updateState(initialState) {
  return updateReducer(basicStateReducer)
;
}

以上就是updateState全部内容,其实update时期调用的竟然是updateReducer,有点意思,接下来看看updateReducer 究竟做了啥,同时这里的basicStateReducer 又是什么。

function updateReducer(reducer, initialArg, init) {
  var hook = updateWorkInProgressHook();
  var queue = hook.queue;   //前面讲了,hook.queue.pending 指向update环转链表的最后一个update,即链表尾部
  if (queue === null) { //mount阶段产生了queue并挂在到了hook上,update阶段 必须有queue否则报错
     throw new Error('Should have a queue. This is likely a bug in React. Please file an issue.');
  }
  queue.lastRenderedReducer = reducer; //这里传的basicStateReducer,后面会讲  var current = currentHook; // The last rebase update that is NOT part of the base state.
    //由于之前某些高优先级任务导致更新中断,baseQueue记录的就是尚未处理的最后一个update
  var baseQueue = current.baseQueue; // The last pending update that hasn't been processed yet.
  var pendingQueue = queue.pending; //当前update链表最后一个update
  if (pendingQueue !== null) {
    if (baseQueue !== null) {      // Merge the pending queue and the base queue.
      //合并上述的baseQueue和pendingQueue
      var baseFirst = baseQueue.next; //未处理的update环转链表的第一个update 
     var pendingFirst = pendingQueue.next; //当前产生的update链表的第一个update
      baseQueue.next = pendingFirst;   //当前产生的更新接入,之前未处理的链表后,baseQueue的尾巴接入pendingQueue的头 
     pendingQueue.next = baseFirst;  //pendingQueue的尾部接入baseQueue的头,最后形成 环
    }
    {
      if (current.baseQueue !== baseQueue) {        // Internal invariant that should never happen, but feasibly could in
        // the future if we implement resuming, or some form of that.
        error('Internal error: Expected work-in-progress queue to be a clone. ' + 'This is a bug in React.');
      }
    }
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }
  if (baseQueue !== null) {    // We have a queue to process. 产生的update链表不为空
    var first = baseQueue.next;     //拿到第一个update
    var newState = current.baseState;  //useState hook当前的state
    var newBaseState = null;
    var newBaseQueueFirst = null;
    var newBaseQueueLast = null;
    var update = first;  //刚开始 update 变量 赋值为 链表中第一个更新
    //这里的do. while 循环则是在遍历刚刚我们合并的update链表,如果baseQueue === null, 不存在

   // 之前的合并操作,整个do{}里面包含的就是 产生心的state的过程
    do {
      var updateLane = update.lane; //lane优先级
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
        var clone = {
          lane: updateLane,
          action: update.action,
          hasEagerState: update.hasEagerState,
          eagerState: update.eagerState,
          next: null
        }; 
       if (newBaseQueueLast === null) {
          newBaseQueueFirst = newBaseQueueLast = clone;          newBaseState = newState;        } else {          newBaseQueueLast = newBaseQueueLast.next = clone;        }         currentlyRenderingFiber$1.lanes = mergeLanes(currentlyRenderingFiber$1.lanes, updateLane);        markSkippedUpdateLanes(updateLane);      } else {        if (newBaseQueueLast !== null) {          var _clone = {            lane: NoLane,            action: update.action,            hasEagerState: update.hasEagerState,            eagerState: update.eagerState,            next: null          };          newBaseQueueLast = newBaseQueueLast.next = _clone;        } // Process this update.        if (update.hasEagerState) {          // If this update is a state update (not a reducer) and was processed eagerly,          // we can use the eagerly computed state          newState = update.eagerState;        } else {
         //核心代码就在这,
          var action = update.action;    //取得当前的update的action,可能是函数也可能是具体的值
          newState = reducer(newState, action);
         }
      }
      update = update.next; //更新update指针指向下一个更新
    } while (update !== null && update !== first); //条件就是把整条环转链表更新完

    if (newBaseQueueLast === null) {
      newBaseState = newState;
    } else {
      newBaseQueueLast.next = newBaseQueueFirst;
    } // Mark that the fiber performed work, but only if the new state is    // different from the current state.
    if (!objectIs(newState, hook.memoizedState)) {      markWorkInProgressReceivedUpdate();
    }
    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;
    queue.lastRenderedState = newState;
  }
   var lastInterleaved = queue.interleaved;
  if (lastInterleaved !== null) {
    var interleaved = lastInterleaved;
    do {
      var interleavedLane = interleaved.lane;
      currentlyRenderingFiber$1.lanes = mergeLanes(currentlyRenderingFiber$1.lanes, interleavedLane);
      markSkippedUpdateLanes(interleavedLane);
      interleaved = interleaved.next;
    } while (interleaved !== lastInterleaved);
  } else if (baseQueue === null) { 
   // `queue.lanes` is used for entangling transitions. We can set it back to    // zero once the queue is empty.
    queue.lanes = NoLanes;
  }
  var dispatch = queue.dispatch;
  return [hook.memoizedState, dispatch];
}

以上为完整代码,看起来有点太多了,精简一下核心常用的代码

function updateReducer(reducer, initialArg, init) {

  var hook = updateWorkInProgressHook();//更新当前执行的hook
  var queue = hook.queue;   //前面讲了,hook.queue.pending 指向update环转链表的最后一个update,即链表尾部
  if (queue === null) { //mount阶段产生了queue并挂在到了hook上,update阶段 必须有queue否则报错
     throw new Error('Should have a queue. This is likely a bug in React. Please file an issue.');
  }
  queue.lastRenderedReducer = reducer; //这里传的basicStateReducer,后面会讲
  var current = currentHook; // The last rebase update that is NOT part of the base state.
    //由于之前某些高优先级任务导致更新中断,baseQueue记录的就是尚未处理的最后一个update
  var baseQueue = current.baseQueue; // The last pending update that hasn't been processed yet.
  var pendingQueue = queue.pending; //当前update链表最后一个update  if (pendingQueue !== null) {
    if (baseQueue !== null) {
      // Merge the pending queue and the base queue.
      //合并上述的baseQueue和pendingQueue
      var baseFirst = baseQueue.next;
     //未处理的update环转链表的第一个update
      var pendingFirst = pendingQueue.next; //当前产生的update链表的第一个update
      baseQueue.next = pendingFirst;
   //当前产生的更新接入,之前未处理的链表后,baseQueue的尾巴接入pendingQueue的头
      pendingQueue.next = baseFirst;  //pendingQueue的尾部接入baseQueue的头,最后形成 环 
   }
 
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }
  if (baseQueue !== null) {
    // We have a queue to process. 产生的update链表不为空
    var first = baseQueue.next;     //拿到第一个update
    var newState = current.baseState;  //useState hook当前的state
    var newBaseState = null;
    var newBaseQueueFirst = null;
    var newBaseQueueLast = null;
    var update = first;  //刚开始 update 变量 赋值为 链表中第一个更新
    //这里的do. while 循环则是在遍历刚刚我们合并的update链表,如果baseQueue === null, 不存在

   // 之前的合并操作,整个do{}里面包含的就是 产生心的state的过程
    do {
      var updateLane = update.lane; //lane优先级
     
       //...more...

        if (update.hasEagerState) {
          // If this update is a state update (not a reducer) and was processed eagerly,
          // we can use the eagerly computed state 
         newState = update.eagerState;
        } else {
         //核心代码就在这,
          var action = update.action;    //取得当前的update的action,可能是函数也可能是具体的值
          newState = reducer(newState, action);
         }
      }
      update = update.next; //更新update指针指向下一个更新
    } while (update !== null && update !== first); //条件就是把整条环转链表更新完

    //把最终得倒的状态更新到 hook上
    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;
    queue.lastRenderedState = newState;
  }
  
  var dispatch = queue.dispatch;
    //返回state和dispatch
  return [hook.memoizedState, dispatch];
}

从新梳理一下

  1. 通过updateWorkInProgressHook 方法更新 当前执行的hook

  2. 把baseQueue和pendingQueue合并,(baseQueue,为之前因为某些原因导致更新中断从而剩下的update链表,pendingQueue则是本次产生的update链表,将他们首位合并形成新的链表,注意在 do. while 循环之前的第一个firstUpdate 变量赋值的是baseQueue.next,说明了整个合并的链表是先执行上一次更新剩下的在执行新的,这样我们的调用拿到的才是正确的数据,举个例子, 这个例子中每次调用setName,我都能拿到上一次的oldState

  3. 通过循环遍历每次产生的newState,作为下一次的参数,直到遍历完整个链表

  4. 最后更新hook上的参数,返回state和dispatch

    const [name,setName] = useState('yangxiang')

    setName((oldState)=>{ return oldState + 'hhaha ' //此时oldState == 'yangxiang' })

    setName((oldState)=>{ return oldState + '奥德曼' //此时oldState == 'yangxianghhaha '})

上述代码中的reducer参数之前说过是basicStateReducer()方法,具体看看他都做了啥

function basicStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action;
}

完整代码就这..., 好吧其实就是判断了一下action是不是函数如果是,则执行函数并把state作为参数,如果不是则直接返回action。

useState简单实现

let isMount = true;let workInProgressHook = null;const fiber = {  memoizedState: null,  stateNode: App};function schedule() {  isMount = false;  workInProgressHook = fiber.memoizedState;  fiber.stateNode();}function useState(initailState) {  let hook;  if (isMount) {    hook = {      queue: {        pending: null      },      memoizedState:        typeof initailState == "function" ? initailState() : initailState,      next: null    };    if (!fiber.memoizedState) {      fiber.memoizedState = hook;    } else {      workInProgressHook.next = hook;    }    workInProgressHook = hook;  } else {    hook = workInProgressHook;    workInProgressHook = workInProgressHook.next;  }  let baseState = hook.memoizedState;  // console.log(hook.queue.pending)  if (hook.queue.pending) {    let currentUpdate = hook.queue.pending.next;    do {      let action = currentUpdate.action;      baseState = action(baseState);      // console.log('baseState',baseState,typeof baseState)      currentUpdate = currentUpdate.next;    } while (currentUpdate != null && currentUpdate != hook.queue.pending.next);    hook.queue.pending = null;  }  hook.memoizedState = baseState;  return [baseState, dispatchSetState.bind(null, hook.queue)];}function dispatchSetState(queue, action) {  // console.log('action-->',action)  let update = {    action: action,    next: null  };  let pending = queue.pending;  if (pending == null) {    update.next = update;  } else {    update.next = pending.next;    pending.next = update;  }  queue.pending = update;  schedule();}function App() {  const [name, setName] = useState(1);  console.log("name", name);  appendDom(`currentValue----> ${name}`);  return {    onclick: () => {      setName((oldVale) => {        console.log("oldValue", oldVale);        appendDom(`oldvalue ---> ` + oldVale);        return oldVale + 1;      });    }  };}let obj = App();obj.onclick();obj.onclick();obj.onclick();obj.onclick();

demo 地址

codesandbox.io/s/condescen…

useReducer

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数

const initialState = {count: 0};
function init(initialCount) {  return {count: initialCount};}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const [state, dispatch] = useReducer(reducer, initialState,init);//惰性初始化
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

这是一个基本使用

mountReducer

function mountReducer(reducer, initialArg, init) {
   var hook = mountWorkInProgressHook(); //初始化hook,和之前 mouState一样的
  var initialState;   
//初始化initialState 赋值 
//按照上方的例子 initialArg = initialState = {count: 0}
  if (init !== undefined) {
    initialState = init(initialArg); //init 是做惰性初始化的具体可看官网
  } else {
    initialState = initialArg;
  }
  hook.memoizedState = hook.baseState = initialState;
  var queue = {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: reducer,
    lastRenderedState: initialState
  };
  hook.queue = queue;
  var dispatch = queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber$1, queue);
  return [hook.memoizedState, dispatch];
}
  1. 和mountState 一样 初始化一个hook,其实其他的hook方法在mount时第一步也是如此。

  2. 判断是否进行惰性加载,然后初始化initialState

  3. hook.memoizedState = hook.baseState = initialState 同时生成一个queue数据结构

  4. 调用dispatchReducerAction,返回dispatch

  5. 最后return return [hook.memoizedState, dispatch];

有上述可知其实和mountState差不多,需要关注的是dispatchReducerAction这个方法。

function dispatchReducerAction(fiber, queue, action) {
  {
    if (typeof arguments[3] === 'function') {
      error("State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + 'rendering, declare it in the component body with useEffect().');
    }
  }
  var lane = requestUpdateLane(fiber);
  var update = {//生成一个update
    lane: lane,
    action: action,
    hasEagerState: false,
    eagerState: null,
    next: null
  };
  if (isRenderPhaseUpdate(fiber)) {
     enqueueRenderPhaseUpdate(queue, update);
   } else {
    enqueueUpdate$1(fiber, queue, update);
    var eventTime = requestEventTime();
    var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    if (root !== null) {
      entangleTransitionUpdate(root, queue, lane);
    }
  }
  markUpdateInDevTools(fiber, lane);
}

其实和之前的在mountState 中调用的 dispatchSetState 方法逻辑是差不多的,做的事情也是差不多的,这里就不再重复的讲了

useEffect

之前在讲解useState阶段曾经说过有个update 数据结构,而在这里 也有一个 特殊的数据结构

effect,他和hook,fiber,的关系 和useState的update差不多

var effect = {
    tag: tag,   //副作用标记
    create: create, //传入的方法
    destroy: destroy, //return的销毁方法
    deps: deps,    // 依赖项
    next: null //下一个effect
  };

mountEffect

话不多说直接看代码

function mountEffect(create, deps) {

  if ( (currentlyRenderingFiber$1.mode & StrictEffectsMode) !== NoMode) {
    return mountEffectImpl(MountPassiveDev | Passive | PassiveStatic, Passive$1, create, deps);
  } else {
    return mountEffectImpl(Passive | PassiveStatic, Passive$1, create, deps);
  }
}

这就是mouthEffect 全部代码,create 参数就是我们传给useEffect(fn,[])的函数,deps参数就是依赖项。

  1. 首先对当前fiber.mode 与 StrictEffectsMode 严格模式进行与运算,其中的区别就是副作用的差别一个多了一个 MountPassiveDev,另一个没有(目前不太理解这一步的具体原因,有知道的大佬麻烦留言告诉我一下)

  2. 之后就是调用mountEffectImpl 这个函数实现

    function mountEffectImpl(fiberFlags, hookFlags, create, deps) { var hook = mountWorkInProgressHook(); var nextDeps = deps === undefined ? null : deps; currentlyRenderingFiber$1.flags |= fiberFlags; hook.memoizedState = pushEffect(HasEffect | hookFlags, create, undefined, nextDeps); }

由这个源代码可知

  1. 首先生成调用mountWorkInProgressHook() 生成一个hook
  2. 判断依赖项是否为空
  3. 进行副作用合并 ,并赋值给fiber
  4. 调用pushEffect(),将useEffect生成的effect 链表挂载到hook.memoizedState上

接下来看pushEffect

function pushEffect(tag, create, destroy, deps) {
  var effect = {
    tag: tag,
    create: create,
    destroy: destroy,
    deps: deps,    // Circular
    next: null
  };
  var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;
  if (componentUpdateQueue === null) {
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    currentlyRenderingFiber$1.updateQueue = componentUpdateQueue;
    //生成环转链表,然后将componentUpdateQueue.lastEffect指针指向effect环状链表的最后一个
    //这里和之前 useState生成 update 换装链表的过程一样
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
        var lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
     //这里同样是生成effect 环状链表的过程
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      //对生成的effect 插入链表尾部的操作 
     var firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}
  1. 生成一个effect 的数据结构
  2. 判断currentlyRenderingFiber$1.updateQueue 是否为空,至于updateQueue上具体是啥,后面你就知道了
  3. 其实就是根据不同条件执行 生成effect 环状链表 ,还是把生成的 effect 插入链表尾部的过程,从上面源码可知对于functionComponent ,fiber.updateQueue.lastEffect指针就是指向当前functionComponent的effect链表的最后一个 effect

createFunctionComponentUpdateQueue()方法,从名字就能看出就是创建一个updateQueue,接下来看看具体怎么实现的

function createFunctionComponentUpdateQueue() {
  return {
    lastEffect: null,
    stores: null
  };
}

好了非常nice,就这。。。

updateEffect

function updateEffect(create, deps) {
  return updateEffectImpl(Passive, Passive$1, create, deps);
}

function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
  var hook = updateWorkInProgressHook();
  var nextDeps = deps === undefined ? null : deps;
  var destroy = undefined;
  if (currentHook !== null) {
   //在之前介绍mountEffectImpl时生成的 effect 是挂载到 hook.memoizedState 属性上的
  //这里拿到之前的 effect ,同时如果有destory方法,也就是我们return的那个方法也拿出来
    var prevEffect = currentHook.memoizedState;
    destroy = prevEffect.destroy;
    if (nextDeps !== null) {
      var prevDeps = prevEffect.deps;
      if (areHookInputsEqual(nextDeps, prevDeps)) {//通过areHookInputsEqual方法比较依赖项是否改变
        hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
        return;
      }
    }
  }
  currentlyRenderingFiber$1.flags |= fiberFlags;
  hook.memoizedState = pushEffect(HasEffect | hookFlags, create, destroy, nextDeps);
}

在updateEffectImpl中

  1. 调用updateWorkInProgressHook(),之前讲过,更新当前正在执行的hook,也就是workInprogressHook
  2. 处理依赖项
  3. 命中if判断后进入其中主要做了,在之前介绍mountEffectImpl时生成的 effect 是挂载到 hook.memoizedState 属性上的 ,这里拿到之前的 effect ,同时如果有destory方法,也就是我们return的那个方法也拿出来,然后通过 areHookInputsEqual(nextDeps,prevDeps)比较前后依赖项有没有改变
  4. 同样是调用pushEffect 方法,和之前讲的一样

useRef

function mountRef(initialValue) {
  var hook = mountWorkInProgressHook();
  {
    var _ref2 = {
      current: initialValue
    };
    hook.memoizedState = _ref2;
    return _ref2;
  }
}

function updateRef(initialValue) {
  var hook = updateWorkInProgressHook();
  return hook.memoizedState;
}

从上面的代码可以知道,通过useRef 创建的 ref ,仅仅就是一个包含 一个 current 属性的 对象,同时挂在到hook 的memoizedState 属性上,update时直接返回 挂载在 hook.memoizedState上的 对象,这也就解释了为什么ref ,在整个组件的生命周期内不会发生改变,除非我们手动的通过 ref.current = 'xxx' 去修改。

useCallback/useMemo

mountCallback/mountMemo

function mountCallback(callback, deps) {
  var hook = mountWorkInProgressHook();
  var nextDeps = deps === undefined ? null : deps;
  hook.memoizedState = [callback, nextDeps];
  return callback;
}
function mountMemo(nextCreate, deps) {
  var hook = mountWorkInProgressHook();
  var nextDeps = deps === undefined ? null : deps;
  var nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

代码一看就懂,区别就是一个执行了函数返回值,一个直接返回函数

updateCallback/updateMemo

function updateCallback(callback, deps) {
  var hook = updateWorkInProgressHook();
  var nextDeps = deps === undefined ? null : deps;
  var prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      var prevDeps = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) { //比较前后依赖 如果相同就返回之前保存的 
       return prevState[0];
      }
    }
  }
  //否则就返回 新的 callback
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

function updateMemo(nextCreate, deps) {
  var hook = updateWorkInProgressHook();
  var nextDeps = deps === undefined ? null : deps;
  var prevState = hook.memoizedState;
  if (prevState !== null) {
    // Assume these are defined. If they're not, areHookInputsEqual will warn.
    if (nextDeps !== null) {
      var prevDeps = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) { //比较 前后依赖项 ,相同返回之前的值
        return prevState[0];
      }
    }
  }
  //否则 返回 新的值,并把新的值和依赖项保存在hook.memoizedState上
  var nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}