Appearance
IndeterminateComponent 组件
不确定类型的组件类型
在从 HostRoot 节点向下遍历处理子孙节点的时候,在 beginWork 阶段都会根据 React.jsx('div' , {} ,[] ) 返回的结果去创建对应节点的 FiberNode 对象,具体在 createFiberFromTypeAndProps()函数中,因为不知道传入的函数具体是一个 函数式组件、存函数 、类组件转换后的函数,所以对于 typeof type === 'function' 的情况,第一次会将其作为一个 未知类型组件(IndeterminateComponent) 去处理。
js
export function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
lanes: Lanes
): Fiber {
// 节点默认类型就是 未知类型组件
let fiberTag = n;
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
let resolvedType = type;
// type 如果是函数类型 那么可能是以下类型
// 1. 函数组件类型
// 2. 存函数类型
// 3. Class 组件类型
// 4. Class组件编译成函数 类型
if (typeof type === "function") {
// 判断是否是 类组件
// 如果 Class.prototype.isReactComponent 那么说明这个肯定是类组件类型的
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
}
}
}从上述代码可以看出,对于创建的组件类型,其主要根据以下条件进行判断
typeof type === 'function'类型
传入的就是一个函数类型, 如 函数式组件
- 字符串类型的组件
对于字符串类型的组件,其一般都是元素类型, 如 <div></div> 这种, 所以对于这种类型的组件,其在编译过程中会将其编译成一个 React.jsx() 函数, 如 <div></div> => { type : 'div', key: null, ref: null, props: { children: null } }
所以这时候我们可以通过 type 去判断是否是字符串类型的组件, 如 typeof type === 'string'
- 内置的组件类型
对于内置的组件类型,其在编译过程中会将其编译成一个 React.jsx() 函数, 如 <></> => { type : React.Fragment , key: null, ref: null, props: { children: null } }
所以这时候我们可以通过 type 去判断是否是内置组件类型, 如 type === REACT_FRAGMENT_TYPE
- type 是对象类型,且存在 type.$$typeof 类型
这种类型主要是以下几种
Context.Provider、Context.Consumer组件 这种可以看 Context 组件 这一届React.xx()处理的组件React.memo()处理的组件React.lazy()处理的组件React.forwardRef()处理的组件
其他的我们都作为 未知类型组件 去处理,其中比较特殊的就是 函数类型的组件, 因为函数类型的组件可能是 函数式组件、存函数、类组件、类组件编译成的函数 ,在第一次编译的时候无法知道具体的类型,所以需要在后面的 updateWork 阶段去判断具体的类型。
但是实际上其将 Class 类型且存在 constructor 的优先判断作为 ClassComponent 类型,所以这个大概率是 FunctionComponent 类型。
所以在处理的时候,也是优先按照 FunctionComponent 类型去处理。
源码
js
/**
* 所有的函数式组件默认第一次都是按照 IndeterminateComponent 类型来处理的
* 在这个类型中会按照函数是组件的方式去执行函数,且根据执行后的返回值判断这个函数究竟是什么类型的组件
* 1. Class 组件经过编译转换后的 函数
* 2. 存函数
* 3. 函数式组件
* 4. module 模式的组件 return { render() { return <div></div> }}
* 5. 内置的组件类型 如 Provider 等
*
* 对于上述几种类型的函数会根据其返回值 value的类型 判断具体类型
* - 存在 value.render 且不存在 $$typeof => Class 类型的组件
* - 其他的将其类型修改成 FunctionComponent
*
*/
function mountIndeterminateComponent(
_current,
workInProgress,
Component,
renderLanes
) {
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
// start: 开始处理 context
const props = workInProgress.pendingProps;
let context;
if (!disableLegacyContext) {
const unmaskedContext = getUnmaskedContext(
workInProgress,
Component,
false
);
context = getMaskedContext(workInProgress, unmaskedContext);
}
prepareToReadContext(workInProgress, renderLanes);
// end: 处理 context
let value;
let hasId;
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
if (__DEV__) {
if (
Component.prototype &&
typeof Component.prototype.render === "function"
) {
const componentName = getComponentNameFromType(Component) || "Unknown";
if (!didWarnAboutBadClass[componentName]) {
console.error(
"The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
"This is likely to cause errors. Change %s to extend React.Component instead.",
componentName,
componentName
);
didWarnAboutBadClass[componentName] = true;
}
}
if (workInProgress.mode & StrictLegacyMode) {
ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
}
setIsRendering(true);
ReactCurrentOwner.current = workInProgress;
value = renderWithHooks(
null,
workInProgress,
Component,
props,
context,
renderLanes
);
hasId = checkDidRenderIdHook();
setIsRendering(false);
} else {
// 直接按照函数组件的方式处理
value = renderWithHooks(
null,
workInProgress,
Component,
props,
context,
renderLanes
);
hasId = checkDidRenderIdHook();
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
// React DevTools reads this flag.
workInProgress.flags |= PerformedWork;
if (__DEV__) {
// Support for module components is deprecated and is removed behind a flag.
// Whether or not it would crash later, we want to show a good message in DEV first.
if (
typeof value === "object" &&
value !== null &&
typeof value.render === "function" &&
value.$$typeof === undefined
) {
const componentName = getComponentNameFromType(Component) || "Unknown";
if (!didWarnAboutModulePatternComponent[componentName]) {
console.error(
"The <%s /> component appears to be a function component that returns a class instance. " +
"Change %s to a class that extends React.Component instead. " +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
"cannot be called with `new` by React.",
componentName,
componentName,
componentName
);
didWarnAboutModulePatternComponent[componentName] = true;
}
}
}
// 根据函数的返回值来判断 函数是什么类型的
// 1. 函数的实例对象返回值 判断是否是 类组件 还是 函数组件
if (
// Run these checks in production only if the flag is off.
// Eventually we'll delete this branch altogether.
// 是否禁用 module 模式的组件 return { render() { return <div></div> }}
!disableModulePatternComponents &&
typeof value === "object" &&
value !== null &&
// 存在 render 函数
typeof value.render === "function" &&
// 不是内置的组件类型
value.$$typeof === undefined
) {
if (__DEV__) {
const componentName = getComponentNameFromType(Component) || "Unknown";
if (!didWarnAboutModulePatternComponent[componentName]) {
console.error(
"The <%s /> component appears to be a function component that returns a class instance. " +
"Change %s to a class that extends React.Component instead. " +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
"cannot be called with `new` by React.",
componentName,
componentName,
componentName
);
didWarnAboutModulePatternComponent[componentName] = true;
}
}
// Proceed under the assumption that this is a class instance
// 组件的类型是 类组件
workInProgress.tag = ClassComponent;
// Throw out any hooks that were used.
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
let hasContext = false;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
workInProgress.memoizedState =
value.state !== null && value.state !== undefined ? value.state : null;
initializeUpdateQueue(workInProgress);
adoptClassInstance(workInProgress, value);
mountClassInstance(workInProgress, Component, props, renderLanes);
return finishClassComponent(
null,
workInProgress,
Component,
true,
hasContext,
renderLanes
);
} else {
// Proceed under the assumption that this is a function component
// 修改组件的类型为 函数组件
workInProgress.tag = FunctionComponent;
if (__DEV__) {
if (disableLegacyContext && Component.contextTypes) {
console.error(
"%s uses the legacy contextTypes API which is no longer supported. " +
"Use React.createContext() with React.useContext() instead.",
getComponentNameFromType(Component) || "Unknown"
);
}
if (
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictLegacyMode
) {
setIsStrictModeForDevtools(true);
try {
value = renderWithHooks(
null,
workInProgress,
Component,
props,
context,
renderLanes
);
hasId = checkDidRenderIdHook();
} finally {
setIsStrictModeForDevtools(false);
}
}
}
if (getIsHydrating() && hasId) {
pushMaterializedTreeId(workInProgress);
}
// 调和子节点
reconcileChildren(null, workInProgress, value, renderLanes);
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, Component);
}
return workInProgress.child;
}
}分析
对于未知类型的,其还是按照通用的一套流程进行处理的,即 1. 处理需要的上下文 context 对象 2. 进行统计性能
然后优先按照 FunctionComponent 处理,所以需要切换 hooks 的上下文去执行函数,最终根据函数的结果来判断具体的类型 1. module 模式的组件 return { render() { return <div></div> }} 2. 是否存在 render 实例对象 , 存在说明这是 Class 类型的组件 3. 是否是内置组件 value.$$typeof === undefined
去判断是否是 Class 类型的组件,如果是 Class 类型的组件又会按照 ClassComponent 的流程执行一遍,并将 workInProgress.tag = ClassComponent;,这样更新的时候就不需要按照未知类型处理了
如果不是,那就是函数式组件了 ,修改组件的类型为函数式组件 workInProgress.tag = FunctionComponent;。
结论
- 对于未知类型的组件主要是处理以下几种情况
经过Babel编译的Class类型的组件,module模式的组件和普通的函数组件,对于前面两种类型的函数,只有执行完成后才知道组件具体是否是ClassComponnet - 对于上述两种特殊类型的组件需要尽量避免,如 Class 类型的组件尽量使用原生的 Class 方法定义,不然初次渲染的时候组件会执行两次从而造成性能浪费
