# render阶段
render阶段主要工作是构建Fiber树和生成effectList链表。而这个过程的起点就是performUnitOfWork函数。react通过循环调用这个函数来为每一个react element对象创建Fiber并通过retuen siblibg child来相连成Fiber树。
# leagcy和concurrent区别
leagcy模式和concurrent模式调用performUnitOfWork的方式有所不同
// leagcy模式
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
// concurrent模式
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
在concurrent模式中会多了一个shouldYield判断当前需不需要暂停。这也就是所说的异步可中断的策略
const frameYieldMs = 5;
let startTime = -1;
function shouldYieldToHost() {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
// The main thread has only been blocked for a really short amount of time;
// smaller than a single frame. Don't yield yet.
return false;
}
}
shouldYieldToHost就是shouldYield函数。通过判断当前时间减去开始时间是否小于5ms(与设备刷新率有关?)来决定是否需要中断本次渲染
# performUnitOfWork大体流程
function performUnitOfWork(unitOfWork) {
var current = unitOfWork.alternate;
var next;
if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
} else {
next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
}
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
var completedWork = unitOfWork;
do {
if ((completedWork.flags & Incomplete) === NoFlags) {
var next = void 0;
if ( (completedWork.mode & ProfileMode) === NoMode) {
next = completeWork(current, completedWork, subtreeRenderLanes);
} else {
next = completeWork(current, completedWork, subtreeRenderLanes);
}
if (next !== null) {
// Completing this fiber spawned new work. Work on that next.
workInProgress = next;
return;
}
} else {
// ...
}
var siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
} // Otherwise, return to the parent
completedWork = returnFiber; // Update the next thing we're working on in case something throws.
workInProgress = completedWork;
} while (completedWork !== null); // We've reached the root.
}
大体上的流程是一个深度优先遍历的过程从RootFiber开始执行beginWork然后顺着rootFiber.child一路向下为每一个子节点执行beginWork。当遍历到最后一个子节点时child为null就会为当前节点执行completeWork。completeWork会寻找该节点的兄弟节点,为兄弟节点执行beiginWork。同样也是一路向下,当遍历到最后一个兄弟节点的时候会一路向上寻找parent。为parent执行completeWork
大致的执行顺序如下
# beginWork
function beginWork(current, workInProgress, renderLanes) {
//current指workInProgress.alternate
var updateLanes = workInProgress.lanes;
if (current !== null) { // 更新状态走这个
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || (
workInProgress.type !== current.type )) { // 判断能否服用条件1
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) { // 判断能否复用条件2
switch (workInProgress.tag) {
case HostRoot:
pushHostRootContext(workInProgress);
resetHydrationState();
break;
case HostComponent:
//...
case ClassComponent:
//...
case HostPortal:
//...
case ContextProvider:
//...
case Profiler:
//...
//...
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
}
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) { // 渲染状态走这个
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
//...
}
}
beginWork会通过workInProgress.alterinate是否为null来判断当前节点是处于首次渲染还是更新。
更新状态
判断节点能否复用。如果能服用则进入bailoutOnAlreadyFinishedWork逻辑。否则会根据workInProgress.tag进入相应的的逻辑最终会调用reconcileChildren创建Fiber节点这个在创建Fiber树那一节有
渲染状态
根据workInProgress.tag进入相应的的逻辑最终会调用reconcileChildren创建Fiber节点。这个在创建Fiber树那一节有
总结一下 beginWork主要阶段就是创建Fiber
# completeWork
function completeWork(current, workInProgress, renderLanes) {
var newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent:
//...
case HostRoot:
//...
case HostComponent:
{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) { // 更新阶段
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
if (current.ref !== workInProgress.ref) {
markRef$1(workInProgress);
}
} else { // 首次渲染
//...
if (_wasHydrated) {
//...
} else {
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress); // 创建dom实例
appendAllChildren(instance, workInProgress, false, false); // 插入子元素
workInProgress.stateNode = instance; // 赋值stateNode
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {//初始化dom属性
markUpdate(workInProgress);
}
}
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef$1(workInProgress);
}
}
return null;
}
case HostText:
//...
case SuspenseComponent:
//...
case HostPortal:
//...
case ContextProvider:
//...
case IncompleteClassComponent:
//...
case SuspenseListComponent:
//...
case FundamentalComponent:
//...
case ScopeComponent:
//...
case Block:
//...
case OffscreenComponent:
case LegacyHiddenComponent:
//...
}
}
completeWork同样根据workInProgress.tag进入不同的逻辑然后判断是首次渲染还是更新(已HostComponent为例)
首次渲染
首先调用createInstance创建对应的dom实例
function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
var parentNamespace;
{
// TODO: take namespace into account when validating.
var hostContextDev = hostContext;
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
if (typeof props.children === 'string' || typeof props.children === 'number') {
var string = '' + props.children;
var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type);
validateDOMNesting(null, string, ownAncestorInfo);
}
parentNamespace = hostContextDev.namespace;
}
var domElement = createElement(type, props, rootContainerInstance, parentNamespace);//这个方法就是创建dom实例。有跟多原生的操作
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}
createInstance中会操作很多原生的document比如createElement
function createElement(type, props, rootContainerElement, parentNamespace) {
var isCustomComponentTag; // We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerElement);
var domElement;
var namespaceURI = parentNamespace;
if (namespaceURI === HTML_NAMESPACE$1) {
namespaceURI = getIntrinsicNamespace(type);
}
if (namespaceURI === HTML_NAMESPACE$1) {
{
isCustomComponentTag = isCustomComponent(type, props); // Should this check be gated by parent namespace? Not sure we want to
// allow <SVG> or <mATH>.
if (!isCustomComponentTag && type !== type.toLowerCase()) {
error('<%s /> is using incorrect casing. ' + 'Use PascalCase for React components, ' + 'or lowercase for HTML elements.', type);
}
}
if (type === 'script') {
// Create the script via .innerHTML so its "parser-inserted" flag is
// set to true and it does not execute
var div = ownerDocument.createElement('div');
div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
// This is guaranteed to yield a script element.
var firstChild = div.firstChild;
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 {
domElement = ownerDocument.createElement(type); // Normally attributes are assigned in
if (type === 'select') {
var node = domElement;
if (props.multiple) {
node.multiple = true;
} else if (props.size) {
// Setting a size greater than 1 causes a select to behave like `multiple=true`, where
// it is possible that no option is selected.
//
// This is only necessary when a select in "single selection mode".
node.size = props.size;
}
}
}
} else {
domElement = ownerDocument.createElementNS(namespaceURI, type);
}
{
if (namespaceURI === HTML_NAMESPACE$1) {
if (!isCustomComponentTag && Object.prototype.toString.call(domElement) === '[object HTMLUnknownElement]' && !Object.prototype.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;
}
以及appendAllChildren,finalizeInitialChildren等也是一样的操作dom元素。具体代码就不贴了
更新阶段
更新阶段主要是调用updateHostComponent$1 方法。这个方法的主要目的是在当前节点的updateQueue也就是任务队列上初始化一个任务。然后提交commitRoot进入commit阶段
updateHostComponent$1 = function (current, workInProgress, type, newProps, rootContainerInstance) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
var oldProps = current.memoizedProps;
if (oldProps === newProps) {
// In mutation mode, this is sufficient for a bailout because
// we won't touch this node even if children changed.
return;
} // If we get updated because one of our children updated, we don't
// have newProps so we'll have to reuse them.
// TODO: Split the update API as separate for the props vs. children.
// Even better would be if children weren't special cased at all tho.
var instance = workInProgress.stateNode;
var currentHostContext = getHostContext(); // TODO: Experiencing an error where oldProps is null. Suggests a host
// component is hitting the resume path. Figure out why. Possibly
// related to `hidden`.
var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext); // TODO: Type this specific to this type of component.
workInProgress.updateQueue = updatePayload; // If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update. All the work is done in commitWork.
if (updatePayload) {
markUpdate(workInProgress);
}
};