React.lazy()解析

2,331 阅读3分钟

React.lazy的使用

React.lazy() 函数是用于实现代码分割和延迟加载的方式。它允许您在组件需要呈现时加载它们的代码,而不是在应用程序加载时立即加载它们。 通过 React.lazy(),您可以按需加载动态导入(Dynamic import)的模块并返回一个包含默认导出组件的 Promise 对象。然后,您可以将此 Promise 对象传递给 <Suspense> 组件,以便在等待该组件呈现时显示 fallback 内容。

React.lazy() 是一个 React Suspense API 的扩展,用于实现组件的延迟加载。它可用于按需加载代码以及优化应用程序的性能。

使用 React.lazy() 进行延迟加载时,需要将要渲染(调用)的组件作为参数传递给 React.lazy() 函数,这个组件必须是使用动态 import() 语法进行封装的。

例如,如果你想延迟加载一个名为 MyComponent 的组件,可以通过以下方式进行:

const MyComponent = React.lazy(() => import('./MyComponent'));

然后,当你需要渲染 MyComponent 组件时,只需像下面这样使用:

<Suspense fallback={<div>Loading...</div>}>
  <MyComponent />
</Suspense>

其中,fallback 属性指定了在加载 MyComponent 组件时显示的占位符 UI,在组件加载完成前,将会渲染 fallback 中定义的内容。

需要注意的是,React.lazy() 只支持默认导出(export default)的组件。如果要渲染命名导出(named exports)的组件,则需要先将其封装在一个默认导出组件中,并且确保导出的组件名称与文件名相同。

React.lazy() 的原理

React.lazy() 的原理是基于 React Suspense API,它使用了 JavaScript 中的动态 import() 语法来实现组件的延迟加载。当一个组件被封装在 React.lazy() 中时,Webpack 会将该组件打包成一个单独的代码块(chunk),并且在需要时才会下载和执行这个代码块。

当渲染延迟加载的组件时,React.lazy() 首先检查是否已经加载了该组件所在的代码块。如果还没有加载,则 React.lazy() 返回一个 Promise,该 Promise 将在下载并执行组件代码块后 resolve 并返回加装好的组件。如果已经加载,则直接返回已加载的组件。

function beginWork() {
  switch (workInProgress.tag) {
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        renderLanes
      );
    }
    // 省略其他case...
  }
}

function mountLazyComponent() {
  const props = workInProgress.pendingProps;
  const lazyComponent: LazyComponentType<any, any> = elementType;
  const payload = lazyComponent._payload;
  
  //调用lazt中的_init进行加载组件
  const init = lazyComponent._init;
  //这里初始化异步加载的组件,调用lazyComponent.init()
  let Component = init(payload);
  // Store the unwrapped component in the type.
  workInProgress.type = Component;
  const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));
  const resolvedProps = resolveDefaultProps(Component, props);
  let child;
  switch (resolvedTag) {
    case FunctionComponent: {
      child = updateFunctionComponent(
        null,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes
      );
      return child;
    }
    case ClassComponent: {
    }

    case ForwardRef: {
    }
    case MemoComponent: {
    }
  }
}


function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;
  // Note: This intentionally doesn't check if we're hydrating because comparing
  // to the current tree provider fiber is just as fast and less error-prone.
  // Ideally we would have a special version of the work loop only
  // for hydration.
  popTreeContext(workInProgress);
  switch (workInProgress.tag) {
    case IndeterminateComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      bubbleProperties(workInProgress);
  }
}
function lazy<T>(
  ctor: () => Thenable<{default: T, ...}>,
): LazyComponent<T, Payload<T>> {
    //payload记录懒加载组件的状态和初始化函数
    //_result中保存组件初始化的函数
  const payload: Payload<T> = {
    // We use these fields to store the result.
    _status: Uninitialized,
    _result: ctor,
  };
  //lazyType中定义的init方法,

  const lazyType: LazyComponent<T, Payload<T>> = {
    $$typeof: REACT_LAZY_TYPE,
    _payload: payload,
    _init: lazyInitializer,
  };


  return lazyType;
}
//lazyInitializer 在函数内部,读取payload._status判断组件是否已经加载,
//若没有加载则进行加载payload._result(),加载完成后改变_status状态,_result保存组件

function lazyInitializer<T>(payload: Payload<T>): T {
//判断是否加载,没有加载就去加载
  if (payload._status === Uninitialized) {
    const ctor = payload._result;
    const thenable = ctor();
    thenable.then(
      moduleObject => {
        if (payload._status === Pending || payload._status === Uninitialized) {
          // Transition to the next state.
          const resolved: ResolvedPayload<T> = (payload: any);
          resolved._status = Resolved;
          resolved._result = moduleObject;
        }
      },
      error => {
        if (payload._status === Pending || payload._status === Uninitialized) {
          // Transition to the next state.
          const rejected: RejectedPayload = (payload: any);
          rejected._status = Rejected;
          rejected._result = error;
        }
      },
    );
    if (payload._status === Uninitialized) {
      // In case, we're still uninitialized, then we're waiting for the thenable
      // to resolve. Set it as pending in the meantime.
      const pending: PendingPayload = (payload: any);
      pending._status = Pending;
      pending._result = thenable;
    }
  }
  if (payload._status === Resolved) {
    const moduleObject = payload._result;
    return moduleObject.default;
  } else {
    throw payload._result;
  }
}

函数 lazyInitializer用于懒加载模块,根据 payload._status 判断模块的加载状态,

  • 如果是 Uninitialized 则调用构造函数 ctor 并返回一个 thenable,即一个可以被 resolve 或 reject 的对象。
  • thenable 被 resolve 时,将结果保存到 payload._result 中,并将 payload._status 设置为 Resolved
  • thenable 被 reject 时,将错误信息保存到 payload._result 中,并将 payload._status 设置为 Rejected
  • 如果 payload._status 还是 Uninitialized,则将 payload._status 设置为 Pending,表示正在等待 thenable 的解决。
  • 如果 payload._statusResolved,则返回 payload._result.default;否则抛出 payload._result