【React源码学习】7 函数式组件触发更新的方式


demo

函数组件触发更新的过程

demo

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

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

    );
}

dispatchAction

onClick调用setC时会触发函数组件的更新,此时调用的实际上是dispatchAction(创建hook时通过dispatchAction.bind绑定到fiber,获取setC)。

触发更新后先创建一个update对象

var update = {
    lane: lane,
    action: action,
    eagerReducer: null,
    eagerState: null,
    next: null
}; 
  • action就是我们传入的state,第一次调用是1
  • eagerReducer会复用上一次reducer
  • eagerState就是这一次要更新的state
if (pending === null) {
    update.next = update;
} else {
    update.next = pending.next;
    pending.next = update;
}
queue.pending = update;

这一部分将update对象挂载到queue.pending上(queue是hook对象的一部分),挂载前检测一下pending上有没有其他的update。

var currentState = queue.lastRenderedState;
var eagerState = lastRenderedReducer(currentState, action); 
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;

这一部分获取新的state(eagerState),复用上一次的reducer,然后通过scheduleUpdateOnFiber调度更新

总结一下这一部分的任务

  1. 创建包含了state的update对象
  2. 将update对象挂载到queue.pending.next上
  3. 用更新update的eagerState和eagerReducer

scheduleUpdateOnFiber

这一部分开始调度我们的更新

markUpdateLaneFromFiberToRoot

这个函数用来合并lane

首先合并update的lane和当前fiber的lane

sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);

lane是一个二进制数据,mergeLanes通过或运算进行合并

function mergeLanes(a, b) {
    return a | b;
}

接下来将lane合并到每个parent.childLanes上,就像下面这样

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;
}
var priorityLevel = getCurrentPriorityLevel()

获取当前任务的优先级,用户触发的优先级是UserBlockingPriority(98),低于ImmediatePriority(99)

总结一下这一阶段的任务

  • 将新的lane合并到fiber.lane上
  • 将新的lane合并到每一层的parent.childLanes上

ensureRootIsScheduled -> Scheduler_scheduleCallback

ensureRootIsScheduled前面都是lane相关的操作,直接看Scheduler_scheduleCallback的调度过程(其实中间还有一个scheduleSyncCallback)。

var currentTime = exports.unstable_now();
var startTime;

先获取当前的时间,然后获取任务开始的时间,如果没有delay的话startTime=currentTime

switch (priorityLevel) {
    case ImmediatePriority:
      timeout = IMMEDIATE_PRIORITY_TIMEOUT;
      break;
    case UserBlockingPriority:
        //some code
    case IdlePriority:
        //some code
    case LowPriority:
        //some code
    case NormalPriority:
    default:
        //some code
}

此时具有最高的优先级ImmediatePriority,timeout(任务可以延迟执行的时间) = -1,立刻执行

var expirationTime = startTime + timeout;

expirationTime获取任务的过期时间,就是任务开始时间 + 可以延迟的时间,此时timeout是-1,说明任务已经过期了,立刻执行

var newTask = {
    id: taskIdCounter++,
    callback: callback,
    priorityLevel: priorityLevel,
    startTime: startTime,
    expirationTime: expirationTime,
    sortIndex: -1
};

创建一个task对象,callback是flushSyncCallbackQueueImpl

if (startTime > currentTime) {
  // some code
} else {
  newTask.sortIndex = expirationTime;
  push(taskQueue, newTask);

  if (!isHostCallbackScheduled && !isPerformingWork) {
    isHostCallbackScheduled = true;
    requestHostCallback(flushWork);
  }
}

由于任务已经过期,直接进入else逻辑,React内部有两个queue,分别是taskQueue和timerQueue,都是heap,task存放的是过期的任务,要立即执行,timerQueue里面的任务还没有过期。这里直接把任务push到taskQueue中

requestHostCallback里面是和postMessage相关的函数

if (!isMessageLoopRunning) {
        isMessageLoopRunning = true;
        port.postMessage(null);
}
var channel = new MessageChannel();
var port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;

也就是说调用port.postMessage后调用performWorkUntilDeadline启动循环,后面就是render了

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