IndeterminateComponent
1 )概述
- 这是一个比较特殊的component的类型, 就是还没有被指定类型的component
- 在一个fibrer被创建的时候,它的tag可能会是
IndeterminateComponent - 在 packages/react-reconciler/src/ReactFiber.js 中,有一个方法
createFiberFromTypeAndProps 中,一开始就声明了let fiberTag = IndeterminateComponent; let resolvedType = type; // 一开始 if (typeof type === 'function') { // 在 function 下只是判断了 constructor是否存在 // 在不存在的时候,是否认为它是一个 FunctionComponent 呢,实际上并没有 // 实际上我们写的任何一个 function component 一开始就是 IndeterminateComponent 类型 if (shouldConstruct(type)) { // 存在,则赋值为 ClassComponent fiberTag = ClassComponent; } } else if (typeof type === 'string') { fiberTag = HostComponent; } else { // 省略 }
- 在最终调用
createFiber 创建 Fiber 对象
2 )源码
定位到 packages/react-reconciler/src/ReactFiber.js
进入 mountIndeterminateComponent 方法
// function mountIndeterminateComponent( _current, workInProgress, Component, renderExpirationTime, ) { // 首先判断 _current 是否存在,存在则进行初始化操作 // 因为只有在第一次渲染的时候,才有 indeterminate component 这种情况 // 经过第一次渲染之后,我们就会发现 indeterminate component 的具体的类型 // 这种情况,可能是中途抛出一个 error 或 promise 等情况,比如 Suspense 组件 // 这时候初始化是为了 去除 _current 和 workInProgress 相关联系,因为需要重新进行初次渲染的流程 if (_current !== null) { // An indeterminate component only mounts if it suspended inside a non- // concurrent tree, in an inconsistent state. We want to treat it like // a new mount, even though an empty version of it already committed. // Disconnect the alternate pointers. _current.alternate = null; workInProgress.alternate = null; // Since this is conceptually a new fiber, schedule a Placement effect workInProgress.effectTag |= Placement; } const props = workInProgress.pendingProps; const unmaskedContext = getUnmaskedContext(workInProgress, Component, false); const context = getMaskedContext(workInProgress, unmaskedContext); prepareToReadContext(workInProgress, renderExpirationTime); prepareToUseHooks(null, workInProgress, renderExpirationTime); let value; if (__DEV__) { if ( Component.prototype && typeof Component.prototype.render === 'function' ) { const componentName = getComponentName(Component) || 'Unknown'; if (!didWarnAboutBadClass[componentName]) { warningWithoutStack( false, "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 & StrictMode) { ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null); } ReactCurrentOwner.current = workInProgress; value = Component(props, context); } else { value = Component(props, context); // 调用 Component 方法 } // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; // 符合这个条件,认为是 ClassComponent, 具有 render 方法 if ( typeof value === 'object' && value !== null && typeof value.render === 'function' && value.$$typeof === undefined ) { // Proceed under the assumption that this is a class instance workInProgress.tag = ClassComponent; // 这里认为它是一个 ClassComponent // Throw out any hooks that were used. resetHooks(); // 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; const getDerivedStateFromProps = Component.getDerivedStateFromProps; if (typeof getDerivedStateFromProps === 'function') { applyDerivedStateFromProps( workInProgress, Component, getDerivedStateFromProps, props, ); } adoptClassInstance(workInProgress, value); mountClassInstance(workInProgress, Component, props, renderExpirationTime); return finishClassComponent( null, workInProgress, Component, true, hasContext, renderExpirationTime, ); } else { // 否则按照 FunctionComponent 来渲染 // Proceed under the assumption that this is a function component workInProgress.tag = FunctionComponent; value = finishHooks(Component, props, value, context); if (__DEV__) { if (Component) { warningWithoutStack( !Component.childContextTypes, '%s(...): childContextTypes cannot be defined on a function component.', Component.displayName || Component.name || 'Component', ); } if (workInProgress.ref !== null) { let info = ''; const ownerName = ReactCurrentFiber.getCurrentFiberOwnerNameInDevOrNull(); if (ownerName) { info += ' Check the render method of `' + ownerName + '`.'; } let warningKey = ownerName || workInProgress._debugID || ''; const debugSource = workInProgress._debugSource; if (debugSource) { warningKey = debugSource.fileName + ':' + debugSource.lineNumber; } if (!didWarnAboutFunctionRefs[warningKey]) { didWarnAboutFunctionRefs[warningKey] = true; warning( false, 'Function components cannot be given refs. ' + 'Attempts to access this ref will fail.%s', info, ); } } if (typeof Component.getDerivedStateFromProps === 'function') { const componentName = getComponentName(Component) || 'Unknown'; if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) { warningWithoutStack( false, '%s: Function components do not support getDerivedStateFromProps.', componentName, ); didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true; } } if ( typeof Component.contextType === 'object' && Component.contextType !== null ) { const componentName = getComponentName(Component) || 'Unknown'; if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) { warningWithoutStack( false, '%s: Function components do not support contextType.', componentName, ); didWarnAboutContextTypeOnFunctionComponent[componentName] = true; } } } reconcileChildren(null, workInProgress, value, renderExpirationTime); return workInProgress.child; } }
- 基于上述判断条件 能认定是一个 ClassComponent,后续渲染一定会按照 ClassComponent 进行
if ( typeof value === 'object' && value !== null && typeof value.render === 'function' && value.$$typeof === undefined ){}
- 现在来测试一下,看下 function component 是否可以执行
import React from 'react' export default function TestIndeterminationComponent() { return { componentDidMount() { console.log('invoker') }, render() { return <span>aaa</span> } } }
- 上述都能正常显示,以及打印 console 输出
- 也就是说,对于一个 function component, 如果里面 return 的对象,具有 render 方法
- 就认为它是一个 class component 一样的类型,去使用它
- 并且在里面声明的生命周期方法都会被它调用
- 这是
IndeterminateComponent 的特性
- 在最初我们渲染的时候,所有的 function component 都是
IndeterminateComponent 的类型 - 在第一次渲染之后,我们根据渲染类型的返回,最终得到具体类型
- 可以通过 function component 返回一个对象的方式去渲染一个类似 classComponent 这样的类型
- 注意,一般不推荐这么写