继续上一遍的内容我们继续说说workLoop是如何将每一个组件的函数渲染成Fiber树的。
Lam:小前端读源码 - React16.7.0(三)
还记得workLoop这个函数吗?
代码语言:javascript复制function workLoop(isYieldy) {
if (!isYieldy) {
// Flush work without yielding
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until there's a higher priority event
while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
因为performUnitOfWork返回了next出来,而next就是一开始根组件的firstUpdate中的element转化成了Fiber对象。所以现在next会作为nextUnitOfWork重新传入performUnitOfWork。其实这里就是一直循环直到performUnitOfWork返回出来null,就代表了整个根组件下的所有嵌套的组件都已经Fiber化了。但是根组件对象和当前的next对象其实会有不一样的,通过断点可以看到next的属性。
我们会发现少了很多东西,例如updateQueue,但是elementType变成了一个函数,那么是否意味着这一次执行performUnitOfWork会有所不一样呢?
接着还是会像之前那样执行,直到进入beginWork函数中时会因为Fiber中的tag不一样的关系导致进入的判断不一样。
代码语言:javascript复制switch (workInProgress.tag) {
case IndeterminateComponent:
{
var elementType = workInProgress.elementType;
return mountIndeterminateComponent(current$$1, workInProgress, elementType, renderExpirationTime);
}
case LazyComponent:
{
var _elementType = workInProgress.elementType;
return mountLazyComponent(current$$1, workInProgress, _elementType, updateExpirationTime, renderExpirationTime);
}
case FunctionComponent:
{
var _Component = workInProgress.type;
var unresolvedProps = workInProgress.pendingProps;
var resolvedProps = workInProgress.elementType === _Component ? unresolvedProps : resolveDefaultProps(_Component, unresolvedProps);
return updateFunctionComponent(current$$1, workInProgress, _Component, resolvedProps, renderExpirationTime);
}
// 进入这里
case ClassComponent:
{
var _Component2 = workInProgress.type;
var _unresolvedProps = workInProgress.pendingProps;
var _resolvedProps = workInProgress.elementType === _Component2 ? _unresolvedProps : resolveDefaultProps(_Component2, _unresolvedProps);
return updateClassComponent(current$$1, workInProgress, _Component2, _resolvedProps, renderExpirationTime);
}
case HostRoot:
return updateHostRoot(current$$1, workInProgress, renderExpirationTime);
case HostComponent:
return updateHostComponent(current$$1, workInProgress, renderExpirationTime);
case HostText:
return updateHostText(current$$1, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current$$1, workInProgress, renderExpirationTime);
case HostPortal:
return updatePortalComponent(current$$1, workInProgress, renderExpirationTime);
case ForwardRef:
{
var type = workInProgress.type;
var _unresolvedProps2 = workInProgress.pendingProps;
var _resolvedProps2 = workInProgress.elementType === type ? _unresolvedProps2 : resolveDefaultProps(type, _unresolvedProps2);
return updateForwardRef(current$$1, workInProgress, type, _resolvedProps2, renderExpirationTime);
}
case Fragment:
return updateFragment(current$$1, workInProgress, renderExpirationTime);
case Mode:
return updateMode(current$$1, workInProgress, renderExpirationTime);
case Profiler:
return updateProfiler(current$$1, workInProgress, renderExpirationTime);
case ContextProvider:
return updateContextProvider(current$$1, workInProgress, renderExpirationTime);
case ContextConsumer:
return updateContextConsumer(current$$1, workInProgress, renderExpirationTime);
case MemoComponent:
{
var _type2 = workInProgress.type;
var _unresolvedProps3 = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
var _resolvedProps3 = resolveDefaultProps(_type2, _unresolvedProps3);
{
if (workInProgress.type !== workInProgress.elementType) {
var outerPropTypes = _type2.propTypes;
if (outerPropTypes) {
checkPropTypes_1(outerPropTypes, _resolvedProps3, // Resolved for outer only
'prop', getComponentName(_type2), getCurrentFiberStackInDev);
}
}
}
_resolvedProps3 = resolveDefaultProps(_type2.type, _resolvedProps3);
return updateMemoComponent(current$$1, workInProgress, _type2, _resolvedProps3, updateExpirationTime, renderExpirationTime);
}
case SimpleMemoComponent:
{
return updateSimpleMemoComponent(current$$1, workInProgress, workInProgress.type, workInProgress.pendingProps, updateExpirationTime, renderExpirationTime);
}
case IncompleteClassComponent:
{
var _Component3 = workInProgress.type;
var _unresolvedProps4 = workInProgress.pendingProps;
var _resolvedProps4 = workInProgress.elementType === _Component3 ? _unresolvedProps4 : resolveDefaultProps(_Component3, _unresolvedProps4);
return mountIncompleteClassComponent(current$$1, workInProgress, _Component3, _resolvedProps4, renderExpirationTime);
}
default:
invariant(false, 'Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue.');
}
之前根组件的时候的tag是等于3的,这个是在创建根Fiber时就已经添加上去的了。那么是什么时候添加上去的呢?其实就是在performUnitOfWork函数中,对子组件实例化的时候最终是调用一个叫createFiberFromTypeAndProps函数,会根据传入进啦的type是什么而分配一个tag,而这个tag就是决定在beginWork函数中以怎样的方式处理组件了。
代码语言:javascript复制function createFiberFromTypeAndProps(type, // React$ElementType
key, pendingProps, owner, mode, expirationTime) {
var fiber = void 0;
var fiberTag = IndeterminateComponent;
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
var resolvedType = type;
// 因为App是一个函数,所以返回1
if (typeof type === 'function') {
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
}
} else if (typeof type === 'string') {
fiberTag = HostComponent;
} else {
getTag: switch (type) {
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(pendingProps.children, mode, expirationTime, key);
case REACT_CONCURRENT_MODE_TYPE:
return createFiberFromMode(pendingProps, mode | ConcurrentMode | StrictMode, expirationTime, key);
case REACT_STRICT_MODE_TYPE:
return createFiberFromMode(pendingProps, mode | StrictMode, expirationTime, key);
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
case REACT_SUSPENSE_TYPE:
return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
default:
{
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;
break getTag;
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
break getTag;
case REACT_LAZY_TYPE:
fiberTag = LazyComponent;
resolvedType = null;
break getTag;
}
}
var info = '';
{
if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
info = ' You likely forgot to export your component from the file ' "it's defined in, or you might have mixed up default and " 'named imports.';
}
var ownerName = owner ? getComponentName(owner.type) : null;
if (ownerName) {
info = 'nnCheck the render method of `' ownerName '`.';
}
}
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 = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.expirationTime = expirationTime;
return fiber;
}
回到beginWork函数内,因为就刚刚所说的,因为现在的workInProgress.tag是等于1,所以就不像根Fiber一样了,根据判断会进入updateClassComponent函数中。
代码语言:javascript复制switch (workInProgress.tag) {
case IndeterminateComponent:
{
var elementType = workInProgress.elementType;
return mountIndeterminateComponent(current$$1, workInProgress, elementType, renderExpirationTime);
}
case LazyComponent:
{
var _elementType = workInProgress.elementType;
return mountLazyComponent(current$$1, workInProgress, _elementType, updateExpirationTime, renderExpirationTime);
}
case FunctionComponent:
{
var _Component = workInProgress.type;
var unresolvedProps = workInProgress.pendingProps;
var resolvedProps = workInProgress.elementType === _Component ? unresolvedProps : resolveDefaultProps(_Component, unresolvedProps);
return updateFunctionComponent(current$$1, workInProgress, _Component, resolvedProps, renderExpirationTime);
}
// 进入这里
case ClassComponent:
{
var _Component2 = workInProgress.type;
var _unresolvedProps = workInProgress.pendingProps;
var _resolvedProps = workInProgress.elementType === _Component2 ? _unresolvedProps : resolveDefaultProps(_Component2, _unresolvedProps);
return updateClassComponent(current$$1, workInProgress, _Component2, _resolvedProps, renderExpirationTime);
}
case HostRoot:
return updateHostRoot(current$$1, workInProgress, renderExpirationTime);
case HostComponent:
return updateHostComponent(current$$1, workInProgress, renderExpirationTime);
case HostText:
return updateHostText(current$$1, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current$$1, workInProgress, renderExpirationTime);
case HostPortal:
return updatePortalComponent(current$$1, workInProgress, renderExpirationTime);
case ForwardRef:
{
var type = workInProgress.type;
var _unresolvedProps2 = workInProgress.pendingProps;
var _resolvedProps2 = workInProgress.elementType === type ? _unresolvedProps2 : resolveDefaultProps(type, _unresolvedProps2);
return updateForwardRef(current$$1, workInProgress, type, _resolvedProps2, renderExpirationTime);
}
case Fragment:
return updateFragment(current$$1, workInProgress, renderExpirationTime);
case Mode:
return updateMode(current$$1, workInProgress, renderExpirationTime);
case Profiler:
return updateProfiler(current$$1, workInProgress, renderExpirationTime);
case ContextProvider:
return updateContextProvider(current$$1, workInProgress, renderExpirationTime);
case ContextConsumer:
return updateContextConsumer(current$$1, workInProgress, renderExpirationTime);
case MemoComponent:
{
var _type2 = workInProgress.type;
var _unresolvedProps3 = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
var _resolvedProps3 = resolveDefaultProps(_type2, _unresolvedProps3);
{
if (workInProgress.type !== workInProgress.elementType) {
var outerPropTypes = _type2.propTypes;
if (outerPropTypes) {
checkPropTypes_1(outerPropTypes, _resolvedProps3, // Resolved for outer only
'prop', getComponentName(_type2), getCurrentFiberStackInDev);
}
}
}
_resolvedProps3 = resolveDefaultProps(_type2.type, _resolvedProps3);
return updateMemoComponent(current$$1, workInProgress, _type2, _resolvedProps3, updateExpirationTime, renderExpirationTime);
}
case SimpleMemoComponent:
{
return updateSimpleMemoComponent(current$$1, workInProgress, workInProgress.type, workInProgress.pendingProps, updateExpirationTime, renderExpirationTime);
}
case IncompleteClassComponent:
{
var _Component3 = workInProgress.type;
var _unresolvedProps4 = workInProgress.pendingProps;
var _resolvedProps4 = workInProgress.elementType === _Component3 ? _unresolvedProps4 : resolveDefaultProps(_Component3, _unresolvedProps4);
return mountIncompleteClassComponent(current$$1, workInProgress, _Component3, _resolvedProps4, renderExpirationTime);
}
default:
invariant(false, 'Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue.');
}
updateClassComponent
这个时候我们的workInProgress并没有将函数中的render构建出具体的结构,所以就需要通过一系列的操作对App这个函数初始化。在函数内会定义一些上下文和一些数据的初始化,之后会执行finishClassComponent函数。
代码语言:javascript复制var nextUnitOfWork = finishClassComponent(current$$1, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime);
finishClassComponent
在finishClasComponent会执行传入组件的render函数。没错,就是class中,render函数。回顾一下进过babel编译后的App是怎样的。
代码语言:javascript复制var App =
/*#__PURE__*/
function (_React$Component3) {
function App() {
_classCallCheck(this, App);
return _possibleConstructorReturn(this, _getPrototypeOf(App).apply(this, arguments));
}
_createClass(App, [{
key: "render",
value: function render() {
return __WEBPACK_IMPORTED_MODULE_5_react___default.a.createElement(H1, null);
}
}]);
_inherits(App, _React$Component3);
return App;
}(__WEBPACK_IMPORTED_MODULE_5_react___default.a.Component);
就是这个render函数了,函数中会调用react.createElement。还记得一开始将App传入ReactDOM.render中前就先经过了react.createElement转换为了ReactElement了。所以现在也会像根组件一样的方式经过react.createElement的洗礼,最终从一个函数转化为一个json。
代码语言:javascript复制class H1 extends React.Component {
static defaultProps = {
text: 'DEMO'
}
render() {
return (<h1>{this.props.text}</h1>)
}
}
class App extends React.Component {
render() {
return (<H1/>)
}
}
最终会复制给nextChildren这个变量。然后会调用reconcileChildren函数,还记得这个函数是干嘛的吗?就是将当前的ReactElement对象转化为一个Fiber对象。并且赋值给workInProgress.child中。
最终返回了以上的对象并且最终返回到performUnitOfWork函数上赋值给了next变量。最终和根对象一样返回到最外面的workLoop中,因为这次App的render后发现是一个H1函数,并且还有render,所以返回出来的child并不为null。所以将App的child,也就是H1的Filber对象保存为全局的nextUnitOfWork变量中,然后又继续调用performUnitOfWork。
因为接下来的H1组件render的时候其实是render原生的html标签,而并非是一个函数,所以还是需要说一下的,但是我们将简化很多流程直接说不一样的地方。
首先到finishClassComponent函数中,返回的ReactElement对象时这样的:
这个时候type就已经不一样了,是一个String而并非是一个函数了。
然后在对ReactElement实例化为一个Fiber的时候,因为判断到type是一个字符串,所以会把当前的Fiber的tag改为5。
然后又回到了workLoop,因为performUnitOfWork还是能返回出一个Fiber对象,所以还是继续会再循环。但是从DEMO判断出,这一次应该是最后一次了。
因为当前的Fiber.tag是5,所以在beginWork函数中,会进入updateHostComponent函数中。
代码语言:javascript复制function updateHostComponent(current$$1, workInProgress, renderExpirationTime) {
pushHostContext(workInProgress);
if (current$$1 === null) {
tryToClaimNextHydratableInstance(workInProgress);
}
var type = workInProgress.type;
var nextProps = workInProgress.pendingProps;
var prevProps = current$$1 !== null ? current$$1.memoizedProps : null;
var nextChildren = nextProps.children;
var isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
// We special case a direct text child of a host node. This is a common
// case. We won't handle it as a reified child. We will instead handle
// this in the host environment that also have access to this prop. That
// avoids allocating another HostText fiber and traversing it.
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
// If we're switching from a direct text child to a normal child, or to
// empty, we need to schedule the text content to be reset.
workInProgress.effectTag |= ContentReset;
}
markRef(current$$1, workInProgress);
// Check the host config to see if the children are offscreen/hidden.
if (renderExpirationTime !== Never && workInProgress.mode & ConcurrentMode && shouldDeprioritizeSubtree(type, nextProps)) {
// Schedule this fiber to re-render at offscreen priority. Then bailout.
workInProgress.expirationTime = Never;
return null;
}
reconcileChildren(current$$1, workInProgress, nextChildren, renderExpirationTime);
return workInProgress.child;
}
因为当前的Fiber是一个原生HTML标签,所以需要判断内部标签内的是什么东西,所以会调用shouldSetTextContent函数判断标签内的是什么东西,因为DEMO中,标签内其实是一个字符串,那么将会把nextChildren设置为null。
代码语言:javascript复制function shouldSetTextContent(type, props) {
return type === 'textarea' || type === 'option' || type === 'noscript' || typeof props.children === 'string' || typeof props.children === 'number' || typeof props.dangerouslySetInnerHTML === 'object' && props.dangerouslySetInnerHTML !== null && props.dangerouslySetInnerHTML.__html != null;
}
再次进入reconcileChildren函数中,因为现在的nextChildren为null,所以在经过一番return后回到performUnitOfWork函数中,判断到next变量为null,就会进入到completeUnitOfWork中。
到这里整个Fiber树就已经构建出来了。
你会发现为什么只有h1的Fiber呢,那么之前的App的Fiber和根Fiber去哪里呢?其实都在return中反转的存在着每个Fiber的父级关机。
下一篇我将讲解如何将Fiber渲染到DOM中。