【React源码学习】3 从setState分析update流程


很简单的demo,一个button而已,看一下setState过程发生了啥

export default class App extends React.Component{

    constructor(props){
        super(props)
        this.state = {value: 0}
    }

    handler(){
        this.setState({value: this.state.value + 1})
    }

    render(){
        return(
            <div className='app'>
                <button onClick={() =>{this.handler()}}>{this.state.value}</button>
            </div>
        )
    }
}

setState -> enqueueSetState

Component.prototype.setState = function (partialState, callback) {
  if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
    {
      throw Error( "setState(...): takes an object of state variables to update or a function which returns an object of state variables." );
    }
  }

  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

这个setState就是component内调用的setState,partialState就是新传入的state({value: 1})。

后面调用enqueueSetState,this就是触发更新的App component

看一下enqueueSetState

function (inst, payload, callback) {
    var fiber = get(inst);
    var eventTime = requestEventTime();
    var lane = requestUpdateLane(fiber);
    var update = createUpdate(eventTime, lane);
    update.payload = payload;

    if (callback !== undefined && callback !== null) {
      {
        warnOnInvalidCallback(callback, 'setState');
      }

      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleUpdateOnFiber(fiber, lane, eventTime);
}

解释一下这几个参数:

  1. inst(instance), 传入的App component
  2. payload,传入的state对象{value: 1}
var eventTime = requestEventTime();
var lane = requestUpdateLane(fiber);

eventTime和react的任务调度相关,事件发生的事件。lane和优先级相关。

调用createUpdate创建一个update对象

function createUpdate(eventTime, lane) {
    var update = {
        eventTime: eventTime,
        lane: lane,
        tag: UpdateState,
        payload: null,
        callback: null,
        next: null
    };
    return update;
}

update对象包含该update的发生事件(eventTime),优先级(lane)等,payload就是新的state,该函数调用结束后手动更新,update之间通过next连接成链表。

enqueueUpdate

fiber上的update是一个环状链表,enqueueUpdate将创建的update插入到该链表中。

fiber上的updateQueue类似这样(如果fiber还没有mount,则updateQueue === null)

{
    baseState: {value: 0}
    effects: null
    firstBaseUpdate: null
    lastBaseUpdate: null
    shared: {pending: null}
}

下面插入update

if (pending === null) {
    update.next = update;
} else {
    update.next = pending.next;
    pending.next = update;
}

如果没有其他update,则新的update自己成环。如果之前有update则将new update插入到上一个创建的update后面构成环。

sharedQueue.pending = update;

将pending指向新的update

scheduleUpdateOnFiber

这个函数用于调度update

markUpdateLaneFromFiberToRoot

markUpdateLaneFromFiberToRoot的代码比较长,贴一下关键的部分

下面是合并lane的部分

parent.childLanes = mergeLanes(parent.childLanes, lane);
//合并new update的lane和fiber自身的lane

alternate.lanes = mergeLanes(alternate.lanes, lane);
//合并同一层的fiber的lane

while (parent !== null) {
    parent.childLanes = mergeLanes(parent.childLanes, lane);
    alternate = parent.alternate;

    if (alternate !== null) {
      alternate.childLanes = mergeLanes(alternate.childLanes, lane);
    } else {
      {
        if ((parent.flags & (Placement | Hydrating)) !== NoFlags) {
          warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
        }
      }
    }

    node = parent;
    parent = parent.return;
}
// 将新的lane不断合并到parend.childLanes上,别忘了同层的fiber也要合并

总结一下合并lane的过程:

  1. 合并新的lane和fiber.lanes
  2. 合并同层fiber(alternate)的lane
  3. 将lane和每一个parent.childLanes合并

markRootUpdate

主要用于将eventTime放入到eventTimes数组内

root.pendingLanes |= updateLane

首先将update.lane合并到root.pendingLanes,也就是说root.pendingLanes保存的是新的update.lane

var eventTimes = root.eventTimes;

对于一个lanes,存在eventTimes表示每一位的过期时间(如果lane的第i位为1,则eventTimes[i] != -1)。

var index = laneToIndex(updateLane);

function pickArbitraryLaneIndex(lanes) {
    return 31 - clz32(lanes);
}

eventTimes[index] = eventTime;

这一段就是将eventTime赋值到eventTimes[i]上。laneToIndex内部调用了pickArbitraryLaneIndex,clz32获取lanes转换为无符号二进制数前面0的个数,换句话说index就是二进制lane中左侧第一个1的位置。接下来就是eventTimes[index] = eventTime

总结一下就是按照lane中1的位置,将eventTime更新到eventTimes数组上。markRootUpdate就是更新每个update的过期时间。

回到scheduleUpdateOnFiber

if (lane === SyncLane) {
    // some code
  } else {
    ensureRootIsScheduled(root, eventTime);
    schedulePendingInteractions(root, lane);

    if (executionContext === NoContext) {
      resetRenderTimer();
      flushSyncCallbackQueue();
    }
  }
} 

用sync模式启动,因此进入lane===SyncLane的逻辑,看一下ensureRootIsScheduled

ensureRootIsScheduled -> markStarvedLanesAsExpired

这个函数将新建update的过期时间更新到expirationTimes上。

while (lanes > 0) {
  var index = pickArbitraryLaneIndex(lanes);
  var lane = 1 << index;
  var expirationTime = expirationTimes[index];

  if (expirationTime === NoTimestamp) {
    if ((lane & suspendedLanes) === NoLanes || (lane & pingedLanes) !== NoLanes) {
      expirationTimes[index] = computeExpirationTime(lane, currentTime);
    }
  } else if (expirationTime <= currentTime) {
    root.expiredLanes |= lane;
  }

  lanes &= ~lane;
}

这一部分比较重要,首先就像markRootUpdate一样计算lanes(pendingLanes,还未被计算)左侧1的位置,然后检测一下expirationTimes在该位置的值,-1表示没有任务,其他值表示过期时间。

if (expirationTime === NoTimestamp) {
      if ((lane & suspendedLanes) === NoLanes || (lane & pingedLanes) !== NoLanes) {
        expirationTimes[index] = computeExpirationTime(lane, currentTime);
      }
    } else if (expirationTime <= currentTime) {
        root.expiredLanes |= lane;
}

这里计算出过期时间后将其赋值给expiredTimes,后面就是每次发现任务过期就执行 performConcurrentWorkOnRoot,具体调度过程下一次详细写一下。

summary

summary


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