前言:为了搞清楚react到底是个什么样的“框架”以及它的内部机制,因此开始阅读react源码。在阅读源码的时候,为了梳理流程,根据自己的理解以及网上的一些资料做了一些(大致流程的)笔记。笔记是当时的理解,写的时候,仍然有很多不理解的地方待后续完善。而写出来的部分,可能会有很多理解不到位甚至是错误的地方,一旦有新的理解或者发现了错误,会补充与修正。
react源码版本:16.8.6
调度更新
react调度更新主要有几种方式:ReactDom.render、setState、forceUpdate;
ReactDom.render则是通过调用updateContainer去执行更新;后两者则分别通过调用enqueueSetState、enqueueForceUpdate去更新。这三个函数很相似,所以只要看其中一个就可以了。
看一下updateContainer函数源码:
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot, // container, 通过render传过来的FiberRoot实例对象
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current; // Fiber实例对象
const currentTime = requestCurrentTime(); // 获取currentTime
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
可以看到它做了这几件事:
① 获取了Fiber对象;
② 计算了currentTime;
③ 计算expirationTime;
④ 调用updateContainerAtExpirationTime并取它的返回值返回;
获取currentTime调用了requestCurrentTime这个函数,看一下这个函数的代码:
代码路径:packages/react-reconciler/src/ReactFiberScheduler.js
// Expiration times are computed by adding to the current time (the start
// time). However, if two updates are scheduled within the same event, we
// should treat their start times as simultaneous, even if the actual clock
// time has advanced between the first and second call.
// In other words, because expiration times determine how updates are batched,
// we want all updates of like priority that occur within the same event to
// receive the same expiration time. Otherwise we get tearing.
let currentEventTime: ExpirationTime = NoWork;
export function requestCurrentTime() {
if (workPhase === RenderPhase || workPhase === CommitPhase) {
// We're inside React, so it's fine to read the actual time.
return msToExpirationTime(now());
}
// We're not inside React, so we may be in the middle of a browser event.
if (currentEventTime !== NoWork) {
// Use the same start time for all updates until we enter React again.
return currentEventTime;
}
// This is the first update since React yielded. Compute a new start time.
currentEventTime = msToExpirationTime(now());
return currentEventTime;
}
这里面有三种情况:
- 如果是处于RenderPhase和CommitPhase阶段,重新获取时间;
- 如果是不是处于RenderPhase ,CommitPhase阶段,而且currentEventTime不处于NoWork就说明react正在处理浏览器事件,React想让来自同一事件的相同优先级的更新的保持相同的时间,因此直接返回之前的时间;
- 如果currentEventTime处于NoWork阶段,就说明是初次更新。计算,更新并返回时间;
然后看一下是怎么计算开始时间的,这里用了一个转化函数msToExpirationTime,将毫秒转化为expirationTime,函数参数是now(),看一下这个now是什么,跳转到:packages/react-reconciler/src/SchedulerWithReactIntegration.js
let initialTimeMs: number = Scheduler_now();
// If the initial timestamp is reasonably small, use Scheduler's `now` directly.
// This will be the case for modern browsers that support `performance.now`. In
// older browsers, Scheduler falls back to `Date.now`, which returns a Unix
// timestamp. In that case, subtract the module initialization time to simulate