理解JSX
对于我们直接书写jsx语法,我们的浏览器是不理解我们这种语法的,所以需要babel来去转义,那么可以通过plugin-transform-react-jsx来转译jsx语法,使得浏览器可以识别我们的Jsx语法,例如:
<div>1<div>
转译过后:
React.createElement("div",null,"1")
对于React.createElement实际是调用的这个方法,来将element的属性收集并返回出一个对象:
const ReactElement = function(type, key, ref, self, source, owner, props) {
// 汇总起来,将这个对象返回出去
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
if (__DEV__) {
// The validation flag is currently mutative. We put it on
// an external backing store so that we can freeze the whole object.
// This can be replaced with a WeakMap once they are implemented in
// commonly used development environments.
element._store = {};
// To make comparing ReactElements easier for testing purposes, we make
// the validation flag non-enumerable (where possible, which should
// include every environment we run tests in), so the test framework
// ignores it.
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false,
});
// self and source are DEV only properties.
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self,
});
// Two elements created in two different places should be considered
// equal for testing purposes and therefore we hide it from enumeration.
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source,
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
上述是创建Element的方法,那么在React中是通过isValidElement这个方法来判断这个Element是否合法的
function isValidElement(object) {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE
);
}
JSX与Fiber的关系
在首屏渲染的时候会创建workingInProgress树,创建节点的依据就是组件返回的jsx对象,而在更新时已经存在了一颗currentFiber树,所以在生成workingInProgress树时,会将组件返回的jsx对象与这个组件的current指针指向的节点做对比,根据对比来生成workingInProgress Fiber,这就是JSX与Fiber的关系
JSX会被编译成React.createElement的执行,理论上来说只要改变了React.createElement方法就能改变页面最终的渲染结果
React Component 与React Element的关系
Component会作为React.createElement方法的第一个参数也就是type参数。
// 这个是一个Class Component
class A {
render(){
return 'A'
}
}
// 这个是一个Function Component
function B(){
}
beginWork与completeWork过程中发生了什么?
mount过程中,起初currentFiber为空,此时先根据jsx对象创建workInProgress Fiber,创建完成后将workInProgress Fiber替换为current Fiber
可以打开浏览器的performance面板来查看React的渲染流程中都调用了什么
对于render阶段开始于renderRootSync方法,对于commit阶段开始于commitRoot方法
在render阶段使用遍历实现了可以中断的递归,其中递归可以分为递阶段与归阶段,在递阶段执行的方法是benginWork,在归阶段执行的方法为completeWork
打开控制台,打开sources,找到react-dom-development.js,搜索beginWork与completeWork两个方法并打上断点,刷新后可以看到当前的current对象中tag值为3,
在ReactWorkTags文件中可以看到,tag为3代表着HostRoot节点,也就是当前应用的根节点
那么在后续的断点调试再调用benginWork的时候我们的current节点都为空,这也就是之前在双缓存中所说的,只有根节点存在current,而其他节点只存在于workInProgress中
workInProgress节点的创建
对于Render阶段的会根据同步更新与异步更新来调用不同的方法作为入口,对于同步更新与异步更新会调用performSyncWorkOnRoot/performConcurrentWorkOnRoot方法来作为入口,接着会调用renderRootSync / renderRootConcurrent方法,在方法内会调用createWorkInProgress方法来初次创建workInProgress Fiber,createWorkInProgress方法源码如下:
// 这里入参中的 current 传入的是现有树结构中的 rootFiber 对象
function createWorkInProgress(current, pendingProps) {
var workInProgress = current.alternate;
// ReactDOM.render 触发的首屏渲染将进入这个逻辑
if (workInProgress === null) {
// 这是需要你关注的第一个点,workInProgress 是 createFiber 方法的返回值
workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// 这是需要你关注的第二个点,workInProgress 的 alternate 将指向 current
workInProgress.alternate = current;
// 这是需要你关注的第三个点,current 的 alternate 将反过来指向 workInProgress
current.alternate = workInProgress;
} else {
// else 的逻辑此处先不用关注
}
// 以下省略大量 workInProgress 对象的属性处理逻辑
// 返回 workInProgress 节点
return workInProgress;
}
创建完后接着会调用workLoop来循环创建workInProgress Fiber,workLoop方法逻辑如下:
// performSyncWorkOnRoot入口 会调用该方法
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
// performConcurrentWorkOnRoot入口 会调用该方法
function workLoopConcurrent() {
// 判断条件是否存在shouldYield的执行,如果浏览器没有足够的时间,那么会终止while循环
// 也不会执行后面的performUnitOfWork函数,自然也不会执行后面的render阶段和commit阶段
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
在performUnitOfWork中会开始调用beginWork/completeWork方法进行下一步Fiber的创建等等
什么是"递"与"归"?
“递”:
递阶段首先会从 rootFiber 开始向下深度优先遍历。遍历到的每个 Fiber 节点,会调用 beginWork 方法,并且该方法会为传入的 Fiber 节点创建它的子 Fiber 节点,并赋值给 workInProgress.child 进行连接,当遍历到叶子节点时就会进入归阶段这个过程也叫做调和
“归”:
就是向上归并的过程,会执行 completeWork 方法来处理 Fiber 节点,当某个 Fiber 节点执行完 completeWork,如果有兄弟 Fiber 节点,会进入该兄弟节点的递阶段。如果不存在兄弟 Fiber 节点,会进入父级节点的归阶段,一直执行到 rootFiber ,期间可以形成 effectList,对于初始化构建会创建 DOM ,对 DOM 事件收集、处理 style等
beginWork
beginWork源码如下,删除了一些部分代码,方便理解
function beginWork(current, workInProgress, renderLanes) {
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
// 来判断新旧props是否有变化,以及是否有Context的变化
// 并且判断workInProgress Fiber与current Fiber中的Dom节点的类型是否改变(比如div变为了span)
// 如果改变了,则进入如下分支
if (oldProps !== newProps || hasContextChanged() || ( // Force a re-render if the implementation changed due to hot reload:
workInProgress.type !== current.type )) {
// 这个值主要来判断在更新中当前Fiber节点是否有变化
didReceiveUpdate = true;
} else {
// props 和 context 没有发生变化,检查是否更新来自自身或者 context 改变
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (
!hasScheduledUpdateOrContext &&
(workInProgress.flags & DidCapture) === NoFlags
) {
didReceiveUpdate = false;
// 这里会调用bailoutOnAlreadyFinishedWork的逻辑,其实来去拦截不需要更新的节点
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
if (getIsHydrating() && isForkedChild(workInProgress)) {
var slotIndex = workInProgress.index;
var numberOfForks = getForksAtLevel();
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
}
......
// 这坨 switch 是 beginWork 中的核心逻辑,原有的代码量相当大
switch (workInProgress.tag) {
......
// 这里省略掉大量形如"case: xxx"的逻辑
// 根节点将进入这个逻辑
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes)
// dom 标签对应的节点将进入这个逻辑
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes)
// 文本节点将进入这个逻辑
case HostText:
return updateHostText(current, workInProgress)
......
// 这里省略掉大量形如"case: xxx"的逻辑
}
// 这里是错误兜底,处理 switch 匹配不上的情况
{
{
throw Error(
"Unknown unit of work tag (" +
workInProgress.tag +
"). This error is likely caused by a bug in React. Please file an issue."
)
}
}
}
mount
在mounted过程中:
对于benginWork:当某一个节点进入benginWork时,他的目的是为了创建当前Fiber节点的第一个子Fiber节点
对于这样的一个结构来讲,在mounted过程中,会先调用benginWork渲染根节点,然后benginWork去渲染App节点,接着benginWork渲染div,然后是header,再接着就是img,可以理解为根据层级依次向下执行benginWork方法来构建Fiber Tree,因为img无子节点,所以此时会执行completeWork方法,这就是一个递归的过程,"递"阶段可以理解为是benginWork阶段,对于归阶段可以理解为completeWork阶段。当img标签执行完completeWork方法后,会判断是否有兄弟Fiber节点,本次例子是有兄弟节点所以此时会执行兄弟节点p标签的benginWork阶段,这个流程其实就是深度优先遍历。对于P节点也会有子节点Edit文本节点,code节点,则与上面的分析方法相同。总结下来是当有子节点时就深入,没有子节点时就遍历兄弟节点,也就是上面所说的深度优先遍历。
按照常理当执行到code节点生成Fiber时,首先去执行benginWork接着子文本节点继续执行benginWork,但是实际是code节点执行完benginWork后,就直接会执行completeWork方法,这是因为对于React中也做了一些优化,比如对于code标签这种只有一个文本子节点的节点,则这个文本节点不会生成自己的Fiber节点,也就不会执行benginWork方法等等。
reconcileChildren
在reconcileChildren中主要通过current是否等于null来判断是首屏渲染还是更新的逻辑,执行调用不同的函数,最终的结果都是将生成的子Fiber节点赋值给workInProgress.child,并作为本次beginWork的返回值,并作为下次performUnitOfWork执行时workInProgress的传参。本次情况是mount的情况此时current为null,则会调用mountChildFibers方法。
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
// 判断 current 是否为 null
if (current === null) {
// 若 current 为 null,则进入 mountChildFibers 的逻辑
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
// 若 current 不为 null,则进入 reconcileChildFibers 的逻辑
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
}
ChildReconciler
对于reconcileChildren方法内调用的两个方法实际是ChildReconciler传入不同的参数来返回的函数,
const reconcileChildFibers = ChildReconciler(true);
const mountChildFibers = ChildReconciler(false);
在ChildReconciler封装了大量的函数来供reconcileChildFibers进行调用操作
function ChildReconciler(shouldTrackSideEffects) {
// 删除节点的逻辑
function deleteChild(returnFiber, childToDelete) {
if (!shouldTrackSideEffects) {
// Noop.
return;
}
// 以下执行删除逻辑
}
......
// 单个节点的插入逻辑
function placeSingleChild(newFiber) {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags = Placement;
}
return newFiber;
}
// 插入节点的逻辑
function placeChild(newFiber, lastPlacedIndex, newIndex) {
newFiber.index = newIndex;
if (!shouldTrackSideEffects) {
// Noop.
return lastPlacedIndex;
}
// 以下执行插入逻辑
}
......
// 此处省略一系列 updateXXX 的函数,它们用于处理 Fiber 节点的更新
// 处理不止一个子节点的情况
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, lanes) {
......
}
// 此处省略一堆 reconcileXXXXX 形式的函数,它们负责处理具体的 reconcile 逻辑
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
// 这是一个逻辑分发器,它读取入参后,会经过一系列的条件判断,调用上方所定义的负责具体节点操作的函数
}
// 将总的 reconcileChildFibers 函数返回
return reconcileChildFibers;
}
对于shouldTrackSideEffects参数代表"是否需要追踪副作用",对于shouldTrackSideEffects的传参不同,则执行不同的逻辑,可以placeSingleChild为例子:
function placeSingleChild(newFiber) {
// 如果shouldTrackSideEffects为false也就是mountChildFibers的逻辑
// 则不会挂载flags
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags = Placement;
}
return newFiber;
}
这里的flags其实也就是EffectTag,对于EffectTag的类型如下:
ReactFiberFlags
在ReactFiberFlags文件中,可以看到会有非常多的节点标记类型,那么在render阶段中我们会为需要操作Dom的Fiber节点打上标记,这个也就是我们常说的EffectTag。
那么常见的类型主要有:
// DOM需要插入到页面中
export const Placement = /* */ 0b00000000000010;
// DOM需要更新
export const Update = /* */ 0b00000000000100;
// DOM需要插入到页面中并更新
export const PlacementAndUpdate = /* */ 0b00000000000110;
// DOM需要删除
export const Deletion = /* */ 0b00000000001000;
比如:Placement 这个 effectTag 的意义,是在渲染器执行时,也就是真实 DOM 渲染时,告诉渲染器:我这里需要新增 DOM 节点。 effectTag 记录的是副作用的类型,而所谓“副作用”,React 给出的定义是“数据获取、订阅或者修改 DOM”等动作。在这里,Placement 对应的显然是 DOM 相关的副作用操作。
reconcileChildFibers
这个函数是一个逻辑分发器,它将根据入参的不同,执行不同的 Fiber 节点操作,最终返回不同的目标 Fiber 节点。
createFiber
createFiber的作用来创建一个Fiber节点
createFiberFromElement:通过ReactElement数据来创建一个Fiber节点
开始benginWork后的所有流程函数:
update
benginWork的源码中主要是通过current是否等于null来判断是首次渲染还是更新的逻辑,通过didReceiveUpdate来判断当前的更新是否源于父级的更新,主要比较的源码如下:
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
// 判断新旧props是否相等,判断context是否发生了改变,判断type是否发生了改变
// context是指无需通过props就能在组件树之间数据传递的方法
// type是指当前标签的类型
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
(__DEV__ ? workInProgress.type !== current.type : false)
) {
didReceiveUpdate = true;
} else {
// props和context没有发生变化,来检测是否来自自身或者context发生改变
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (
!hasScheduledUpdateOrContext &&
// If this is the second pass of an error or suspense boundary, there
// may not be work scheduled on `current`, so we check for this flag.
(workInProgress.flags & DidCapture) === NoFlags
) {
// No pending updates or context. Bail out now.
didReceiveUpdate = false;
// 这里会调用bailoutOnAlreadyFinishedWork的逻辑,其实来去拦截不需要更新的节点
// 在bailoutOnAlreadyFinishedWork方法中会调用cloneChildFibers来复制一份Fiber节点
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// This is a special case that only exists for legacy mode.
// See https://github.com/facebook/react/pull/19216.
didReceiveUpdate = true;
} else {
// An update was scheduled on this fiber, but there are no new props
// nor legacy context. Set this to false. If an update queue or context
// consumer produces a changed value, it will set this to true. Otherwise,
// the component will assume the children have not changed and bail out.
didReceiveUpdate = false;
}
}
}
checkScheduledUpdateOrContext
检查当前 Fiber 节点上的 lanes 是否等于 updateLanes,如果相等,那么证明更新来源当前 fiber 返回 true
function checkScheduledUpdateOrContext(current, renderLanes) {
var updateLanes = current.lanes;
if (includesSomeLane(updateLanes, renderLanes)) {
return true;
} // No pending update, but because context is propagated lazily, we need
return false;
}
当 checkScheduledUpdateOrContext函数返回 false,则证明当前组件没有更新,context 又没有变化,只能是子节点更新。会进入 attemptEarlyBailoutIfNoScheduledUpdate 的逻辑,在这个逻辑中会根据不同的 type 来复用 Fiber 节点
attemptEarlyBailoutIfNoScheduledUpdate
attemptEarlyBailoutIfNoScheduledUpdate这个函数会处理部分 Context 逻辑,但是最重要的是调用了 bailoutOnAlreadyFinishedWork方法
function attemptEarlyBailoutIfNoScheduledUpdate(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
switch (workInProgress.tag) {...}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
bailoutOnAlreadyFinishedWork
主要目的是来检查子树需要不需要更新,首先通过 includesSomeLane来判断 childLanes 是否是高优先级的任务,如果不是,则子孙节点不需要被调和。
在includesSomeLane来判断当前 Fiber 节点的子孙节点中,有没有需要在本次 render 过程中进行的更新任务,如果没有,则可以直接跳过当前节点下所有后代节点的 render
若后代节点中仍有本次 render 过程需要处理的更新任务,则克隆 current 树上对应的子 Fiber 节点并返回,作为下次 performUnitOfWork 的主体,但组件本身不会 rerender
function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {
if (current !== null) {
workInProgress.dependencies = current.dependencies;
}
{
stopProfilerTimerIfRunning();
}
markSkippedUpdateLanes(workInProgress.lanes);
// 如果 children 没有高优先级的任务,说明所有的 child 没有更新,那么child 不需要被调和
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
{
return null;
}
}
// 当前fiber没有更新。但是它的children 需要更新
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
cloneChildFibers
作用是复用current Fiber Tree上对应的子Fiber节点
function cloneChildFibers(current, workInProgress) {
if (current !== null && workInProgress.child !== current.child) {
throw new Error('Resuming work not yet implemented.');
}
if (workInProgress.child === null) {
return;
}
var currentChild = workInProgress.child;
var newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
// 将新建的子Fiber节点与子节点建立联系
workInProgress.child = newChild;
newChild.return = workInProgress;
// 遍历当前Fiber子节点的所有兄弟节点,赋值给我们新创建的节点newChild,来进行节点复用
while (currentChild.sibling !== null) {
currentChild = currentChild.sibling;
newChild = newChild.sibling = createWorkInProgress(currentChild, currentChild.pendingProps);
newChild.return = workInProgress;
}
newChild.sibling = null;
}
对于benginWork的update流程中最核心且主要的工作是bailoutOnAlreadyFinishedWork,通过baiout来将与本次无关更新的Fiber树路径进行剪枝,然后将其进行复用,这种复用会保留本次剪枝中Fiber子树的所有子节点
completeWork
- 根据workInProgress.tag进入不同函数,我们以HostComponent举例
- update时(除了判断current=null外还需要判断workInProgress.stateNode=null),调用updateHostComponent处理props(包括onClick、style、children …),并将处理好的props赋值给updatePayload,最后会保存在workInProgress.updateQueue上
- mount时 调用createInstance创建dom,将后代dom节点插入刚创建的dom中,调用finalizeInitialChildren处理props(和updateHostComponent处理的逻辑类似)
mount
对于Dom节点的创建与更新主要在completeWork函数中,大部分的创建都是会在HostComponent的情况中体现,因此我们可以看一下HostComponent对应的部分
case HostComponent:{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
// 首先判断current是否存在,在首屏渲染时current是不存在的所以会进入else的逻辑
if (current !== null && workInProgress.stateNode != null) {
// 更新dom节点
updateHostComponent(current, workInProgress, type, newProps, rootContainerInstance);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
// 首屏渲染的逻辑
// 新增dom节点
if (!newProps) {
if (workInProgress.stateNode === null) {
throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');
}
bubbleProperties(workInProgress);
return null;
}
var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
// Hydrated是跟ssr相关的所以暂时不需要管
var _wasHydrated = popHydrationState(workInProgress);
if (_wasHydrated) {
// 服务端渲染相关,本次不关注
} else {
// 在createInstance这个方法中主要用于创建对应的Dom节点
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
// 当创建完Dom节点后,插入到之前已经创建好的Dom树中,这一步主要在appendAllChildren中执行
appendAllChildren(instance, workInProgress, false, false);
// 把创建的Dom节点保存在stateNode属性身上
workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount.
// 为Dom节点设置Props属性
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
markUpdate(workInProgress);
}
}
if (workInProgress.ref !== null) {
markRef$1(workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
}
createInstance
在这个方法中主要用于创建Dom节点,核心还是通过createElement方法传入我们所定义的type、props等一些属性来创建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;
}
// 通过createElement来创建一个Dom节点,
var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}
appendAllChildren
appendAllChildren的方法的作用是,每次调用这个方法时都会将已经创建好的Dom节点挂载到当前父级的Dom节点下,在completeWork依次向上"归"的过程,就将当前的节点挂载到父节点上。
// Mutation mode
appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
var node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (node.tag === HostPortal) ; else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
finalizeInitialChildren
function finalizeInitialChildren(domElement, type, props, rootContainerInstance, hostContext) {
setInitialProperties(domElement, type, props, rootContainerInstance);
// 判断dom节点的类型,如果带focus属性的Dom则返回props的autoFocus(布尔值)
switch (type) {
case 'button':
case 'input':
case 'select':
case 'textarea':
return !!props.autoFocus;
case 'img':
return true;
default:
return false;
}
}
setInitialProperties
在这个函数中主要用于初始化props属性,调用这个方法后又会调用setValueForProperty执行方法,最后通过setAttribute来设置属性
function setInitialProperties(domElement, tag, rawProps, rootContainerElement) {
// 首先判断是否是一个自定义的标签
var isCustomComponentTag = isCustomComponent(tag, rawProps);
{
validatePropertiesInDevelopment(tag, rawProps);
} // TODO: Make sure that we check isMounted before firing any of these events.
var props;
// 根据不同的标签执行添加不同的初始属性与监听器
switch (tag) {
case 'dialog':
listenToNonDelegatedEvent('cancel', domElement);
listenToNonDelegatedEvent('close', domElement);
props = rawProps;
break;
case 'iframe':
case 'object':
case 'embed':
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the load event.
listenToNonDelegatedEvent('load', domElement);
props = rawProps;
break;
case 'video':
case 'audio':
// We listen to these events in case to ensure emulated bubble
// listeners still fire for all the media events.
for (var i = 0; i < mediaEventTypes.length; i++) {
listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
}
props = rawProps;
break;
case 'source':
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the error event.
listenToNonDelegatedEvent('error', domElement);
props = rawProps;
break;
case 'img':
case 'image':
case 'link':
// We listen to these events in case to ensure emulated bubble
// listeners still fire for error and load events.
listenToNonDelegatedEvent('error', domElement);
listenToNonDelegatedEvent('load', domElement);
props = rawProps;
break;
case 'details':
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the toggle event.
listenToNonDelegatedEvent('toggle', domElement);
props = rawProps;
break;
case 'input':
initWrapperState(domElement, rawProps);
props = getHostProps(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
break;
case 'option':
validateProps(domElement, rawProps);
props = rawProps;
break;
case 'select':
initWrapperState$1(domElement, rawProps);
props = getHostProps$1(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
break;
case 'textarea':
initWrapperState$2(domElement, rawProps);
props = getHostProps$2(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
break;
default:
props = rawProps;
}
// 在这个函数中主要来判断我们的props是否合法
assertValidProps(tag, props);
// 合法的话,我们就初始化对应的dom属性
// 在这个方法中又会调用setValueForProperty方法来执行,最后通过setAttribute来设置属性
setInitialDOMProperties(tag, domElement, rootContainerElement, props, isCustomComponentTag);
switch (tag) {
case 'input':
// TODO: Make sure we check if this is still unmounted or do any clean
// up necessary since we never stop tracking anymore.
track(domElement);
postMountWrapper(domElement, rawProps, false);
break;
case 'textarea':
// TODO: Make sure we check if this is still unmounted or do any clean
// up necessary since we never stop tracking anymore.
track(domElement);
postMountWrapper$3(domElement);
break;
case 'option':
postMountWrapper$1(domElement, rawProps);
break;
case 'select':
postMountWrapper$2(domElement, rawProps);
break;
default:
if (typeof props.onClick === 'function') {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(domElement);
}
break;
}
}
updated
当update时,Fiber节点已经存在对应的DOM节点,所以不需要生成DOM节点。需要做的主要是处理props。
case HostComponent:{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
// 首先判断current是否存在,在更新阶段时current存在的,所以通过此逻辑来判断是否为更新阶段
if (current !== null && workInProgress.stateNode != null) {
// 更新dom节点
updateHostComponent(current, workInProgress, type, newProps, rootContainerInstance);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
}
}
updateHostComponent
通过此方法来对比新旧props,返回一个需要更新属性名称的数组[key1,value1,key2,value2],并将其赋值给workInProgress.updateQueue,至此render过程的update就执行完毕了
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
type: Type,
newProps: Props,
) {
const oldProps = current.memoizedProps;
// 判断新旧props是否相同,如果相同则直接不进行后面的逻辑
if (oldProps === newProps) {
return;
}
const instance: Instance = workInProgress.stateNode;
const currentHostContext = getHostContext();
// diff新旧props,获取对应的变化
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
currentHostContext,
);
// 将处理完的props以数组的形式
workInProgress.updateQueue = (updatePayload: any);
if (updatePayload) {
markUpdate(workInProgress);
}
};
总结
React的更新任务主要是调用一个叫做workLoop的工作循环去构建workInProgress树,构建过程分为两个阶段:向下遍历和向上回溯,向下和向上的过程中会对途径的每个节点进行beginWork和completeWork。