在 React源码解析之FunctionComponent(中) 提到了createFiberFromElement
:
const created = createFiberFromElement(
element,
returnFiber.mode,
expirationTime,
);
createFiberFromElement
里面又调用了createFiberFromTypeAndProps
:
//通过type和 props 来创建 fiber
export function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
expirationTime: ExpirationTime,
): Fiber {
let fiber;
//附一个默认值
//只有 FunctionComponent 没有再次设 fiberTag,所以它的默认 tag 就是 IndeterminateComponent
let fiberTag = IndeterminateComponent;
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
let resolvedType = type;
if (typeof type === 'function') {
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
if (__DEV__) {
resolvedType = resolveClassForHotReloading(resolvedType);
}
} else {
if (__DEV__) {
resolvedType = resolveFunctionForHotReloading(resolvedType);
}
}
} else if (typeof type === 'string') {
//<div></div>
fiberTag = HostComponent;
} else {
//创建 react 的内置组件
getTag: switch (type) {
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(
pendingProps.children,
mode,
expirationTime,
key,
);
case REACT_CONCURRENT_MODE_TYPE:
fiberTag = Mode;
mode |= ConcurrentMode | BatchedMode | StrictMode;
break;
case REACT_STRICT_MODE_TYPE:
fiberTag = Mode;
mode |= StrictMode;
break;
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
case REACT_SUSPENSE_TYPE:
return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
case REACT_SUSPENSE_LIST_TYPE:
return createFiberFromSuspenseList(
pendingProps,
mode,
expirationTime,
key,
);
default: {
//React.createRef,React.createContext返回的均为 object
//根据不同的类型,赋 fibertag
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break getTag;
case REACT_CONTEXT_TYPE:
// This is a consumer
fiberTag = ContextConsumer;
break getTag;
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
if (__DEV__) {
resolvedType = resolveForwardRefForHotReloading(resolvedType);
}
break getTag;
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
break getTag;
case REACT_LAZY_TYPE:
fiberTag = LazyComponent;
resolvedType = null;
break getTag;
case REACT_EVENT_COMPONENT_TYPE:
if (enableFlareAPI) {
return createFiberFromEventComponent(
type,
pendingProps,
mode,
expirationTime,
key,
);
}
break;
}
}
let info = '';
//删除了 dev 代码
//如果走到这里,说明 type 不是 React 内置的类型,报警告
invariant(
false,
'Element type is invalid: expected a string (for built-in ' +
'components) or a class/function (for composite components) ' +
'but got: %s.%s',
type == null ? type : typeof type,
info,
);
}
}
}
//创建 fiber 对象
fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.expirationTime = expirationTime;
return fiber;
}
我们重点注意一下fiberTag
:
//只有 FunctionComponent 没有再次设 fiberTag,所以它的默认 tag 就是 IndeterminateComponent
let fiberTag = IndeterminateComponent;
从这段代码可以看到,fiberTag
的默认值是IndeterminateComponent,
随后有赋值成ClassComponent
/HostComponent
/Mode
/ContextProvider
/ContextConsumer
/ForwardRef
/MemoComponent
/LazyComponent
等,
但唯独没有FunctionComponent
的 tag 类型,
那么 React源码解析之FunctionComponent(上) 中怎么会有fiberTag = FunctionComponent
的更新呢?
答案就在mountIndeterminateComponent
中:
//如果节点是有更新的
//根据节点类型进行组件的更新
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderExpirationTime,
);
}
xxx...
xxx...
}
也就是说, FunctionComponent一开始的状态是IndeterminateComponent,经过mountIndeterminateComponent()后,才得到了FunctionComponent的fiberTag
接下来我们就看下mountIndeterminateComponent
作用:
进一步明确FunctionComponent
以何种方式更新
源码:
//进一步明确 FunctionComponent 以何种方式更新
function mountIndeterminateComponent(
_current,
workInProgress,
Component,
renderExpirationTime,
) {
//只有在第一次渲染的时候,才会调用mountIndeterminateComponent(),此时_current应该为 null
//出现_current不为 null 的情况,一般是第一次渲染的时候捕获到 error 了,此时就需要重置_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;
//=========context 可跳过===========================================================
const unmaskedContext = getUnmaskedContext(workInProgress, Component, false);
const context = getMaskedContext(workInProgress, unmaskedContext);
prepareToReadContext(workInProgress, renderExpirationTime);
prepareToReadEventComponents(workInProgress);
//=======================================================================
let value;
if (__DEV__) {
//删除了 dev 代码
} else {
//因为FunctionComponent一开始是处于indeterminateComponent的状态下的,所以会涉及到 hooks
//渲染的过程中,对里面用到的 hook函数做一些操作
//renderWithHooks的解析请看 React源码解析之FunctionComponent(上):
//https://juejin.im/post/5ddbe114e51d45231e010c75
value = renderWithHooks(
null,
workInProgress,
Component,
props,
context,
renderExpirationTime,
);
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
//确认是否是 ClassComponent,因为只有ClassComponent有 render() 方法
//关键是这个判断条件
//如果这个条件成立的话,就表明可以在 FunctionComponent 中使用 ClassComponent 的 API !!
if (
typeof value === 'object' &&
value !== null &&
typeof value.render === 'function' &&
value.$$typeof === undefined
) {
//删除了 dev 代码
// Proceed under the assumption that this is a class instance
workInProgress.tag = ClassComponent;
// Throw out any hooks that were used.
// 重置 hooks 状态,也就是不使用 hooks
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.
//下面的这些 function 在 updateClassComponent() 中都有解析过,就不再赘述了
//https://juejin.im/post/5e1bc74ee51d45020837e8f4
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,
);
}
//否则就是 FunctionComponent
else {
// Proceed under the assumption that this is a function component
//正式赋予 tag 为 FunctionComponent,将按照FunctionComponent的流程更新组件
workInProgress.tag = FunctionComponent;
//删除了 dev 代码
//reconcileChildren的解析请看:React源码解析之FunctionComponent(上)
//https://juejin.im/post/5ddbe114e51d45231e010c75
reconcileChildren(null, workInProgress, value, renderExpirationTime);
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, Component);
}
return workInProgress.child;
}
}
解析:
(1) mountIndeterminateComponent()
是在组件第一次渲染的时候调用的,
此时_current
应为null
只有当组件被挂起(suspended
)的时候,_current
才不为null
,
而suspended
的情况一般是捕获到了error
,所以此时就需要重置_current
和workInProgress
部分属性
(2) 关于renderWithHooks()
的解析请看:
React源码解析之FunctionComponent(上)
(3) resetHooks()
的作用是重置 hooks 状态,源码比较简单,就不解析了:
// 重置 hooks 状态
export function resetHooks(): void {
// We can assume the previous dispatcher is always this one, since we set it
// at the beginning of the render phase and there's no re-entrancy.
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
// This is used to reset the state of this module when a component throws.
// It's also called inside mountIndeterminateComponent if we determine the
// component is a module-style component.
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
currentHook = null;
nextCurrentHook = null;
firstWorkInProgressHook = null;
workInProgressHook = null;
nextWorkInProgressHook = null;
//删除了 dev 代码
remainingExpirationTime = NoWork;
componentUpdateQueue = null;
sideEffectTag = 0;
didScheduleRenderPhaseUpdate = false;
renderPhaseUpdates = null;
numberOfReRenders = 0;
}
(4) 重点看下这个判断条件:
if (
typeof value === 'object' &&
value !== null &&
typeof value.render === 'function' &&
value.$$typeof === undefined
){
}
value
在开发角度上讲,应该是指FunctionComponent
中return
返回的值,
如果value
中包含render()
方法的话,则按ClassComponent
的方式更新组件,
否则就确认为真正的FunctionComponent
,tag
赋值为FunctionComponent
(5) 当 (4) 判断为true
时,里面执行的一些function
的解析请看:
React源码解析之updateClassComponent(上)
(6) 当 (4) 判断为false
时,里面执行的一些function
的解析请看:
React源码解析之FunctionComponent(上)
按照 (4) 的情况,是不是在FunctionComponent
中写render()
方法后,就能调用ClassComponent
的 API 呢?
我们来试下:
import React from "react";
import { Dispatch } from "redux";
interface TestProps {
dispatch: Dispatch<any>;
}
const Test: React.FC<TestProps> = props => {
const { dispatch, } = props;
return {
componentWillMount(){
console.log('在FunctionComponent中调用了componentWillMount')
},
render() {
return (
<div>
在 FunctionComponent 中调用 ClassComponent 的 API
</div>
)
}
}
};
效果图:
可以看到,在FunctionComponent中是可以调用ClassComponent的API的!
当然,React 也报了warning
,不建议开发者使用这种 hack 手段去编写代码。
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberBeginWork.js
小进进还没开通留言功能,觉得不错的话,点「在看」、转发朋友圈都是一种支持 (●'◡'●)ノ 。 欢迎关注本公众号:
(完)