React以往版本已有Suspense本次改版并没有多大改变。

Suspense

首次挂在时还是会返回Suspense组件,如果遇到了throw(Lazy)的Promise会被捕获到然后一致像父级查找最近的第一个Suspense组件

  1. do {
  2. try {
  3. workLoopSync();
  4. break;
  5. } catch (thrownValue) {
  6. // 捕获错误
  7. handleError(root, thrownValue);
  8. }
  9. } while (true);
  10. function throwException(root, returnFiber, sourceFiber, value, rootRenderLanes) {
  11. // The source fiber did not complete.
  12. sourceFiber.flags |= Incomplete;
  13. // 判断错误是否是一个Promise对象
  14. if (value !== null && typeof value === 'object' && typeof value.then === 'function') {
  15. var wakeable = value;
  16. // 向上查找父级suspense组件
  17. var suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber);
  18. if (suspenseBoundary !== null) {
  19. // code...
  20. //添加ping
  21. attachRetryListener(suspenseBoundary, root, wakeable);
  22. return;
  23. }
  24. // 处理错误, 所以如果没有被包裹就会报错
  25. }
  26. function attachPingListener(root: FiberRoot, wakeable: Wakeable, lanes: Lanes) {
  27. let pingCache = root.pingCache;
  28. // 添加关联的id
  29. let threadIDs;
  30. if (pingCache === null) {
  31. pingCache = root.pingCache = new PossiblyWeakMap();
  32. threadIDs = new Set();
  33. pingCache.set(wakeable, threadIDs);
  34. } else {
  35. threadIDs = pingCache.get(wakeable);
  36. if (threadIDs === undefined) {
  37. threadIDs = new Set();
  38. pingCache.set(wakeable, threadIDs);
  39. }
  40. }
  41. // 只有第一个promise才会被处理
  42. if (!threadIDs.has(lanes)) {
  43. threadIDs.add(lanes);
  44. const ping = pingSuspendedRoot.bind(null, root, wakeable, lanes);
  45. // 也就是 promise().then(func, function)
  46. wakeable.then(ping, ping);
  47. }
  48. }
  49. export function pingSuspendedRoot(
  50. root: FiberRoot,
  51. wakeable: Wakeable,
  52. pingedLanes: Lanes,
  53. ) {
  54. const pingCache = root.pingCache;
  55. if (pingCache !== null) {
  56. // 此时promise已经执行完毕,从缓存中删除
  57. pingCache.delete(wakeable);
  58. }
  59. const eventTime = requestEventTime();
  60. markRootPinged(root, pingedLanes, eventTime);
  61. // 同时存在两个ping
  62. if (
  63. workInProgressRoot === root &&
  64. isSubsetOfLanes(workInProgressRootRenderLanes, pingedLanes)
  65. ) {
  66. // 重新设置一次ping
  67. if (
  68. workInProgressRootExitStatus === RootSuspendedWithDelay ||
  69. (workInProgressRootExitStatus === RootSuspended &&
  70. includesOnlyRetries(workInProgressRootRenderLanes) &&
  71. now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
  72. ) {
  73. // 准备一次新的调用环境
  74. prepareFreshStack(root, NoLanes);
  75. } else {
  76. // 合并 pending及已经存在的lane
  77. workInProgressRootPingedLanes = mergeLanes(
  78. workInProgressRootPingedLanes,
  79. pingedLanes,
  80. );
  81. }
  82. }
  83. // 开始调度一次更新
  84. ensureRootIsScheduled(root, eventTime);
  85. schedulePendingInteractions(root, pingedLanes);
  86. }

由于我们的异步还没有执行完毕,也就是Promise的状态没有改变,所以在遇到Sunpense组件时,下面的子fiber节点,被抛出后并不会再继续工作创建fiber节点,而是返回fallback组件。等到promise状态改变以后,从根节点再发起一次更新也就是ensureRootIsScheduled 函数的调用

这个函数是整个应用的第二个入口,第一个是schedulerUpdateOnFiber❤️

throwException添加ping后,当前的unitWork可能是LazyComponent,这里promise被当成错误被抛出,Lazy
是异步组件,此时还没有加载完毕,所以没有必要在继续Lazy及子组件的beginWork. Lazy到Susponse中间的组件已经走过了beginWork, 但是现在要在Lazy开始走complete过层呢, 所以这里走的completeWork,为lazy和向上第一个Susponse组件中间的组件走complete过程,因为这些组件进行了beginWork但是不一定全是异步组件,也可能是HostComponent。

Lazy

lazy是React提供接收异步组件的api,返回的组件是LazyComponent


function mountLazyComponent(_current, workInProgress, elementType, renderLanes) {
  if (_current !== null) {

    _current.alternate = null;
    workInProgress.alternate = null; // Since this is conceptually a new fiber, schedule a Placement effect

    workInProgress.flags |= Placement;
  }

  var props = workInProgress.pendingProps;
  var lazyComponent = elementType;
  // payload 是防止lazyCompoent身上的
  // 是个对象和Context的provader组件蕾丝
  var payload = lazyComponent._payload;
  var init = lazyComponent._init;
  // lazy 内部传入的init函数
  // init函数是内部放的
  var Component = init(payload); // Store the unwrapped component in the type.

  workInProgress.type = Component;
  var resolvedTag = workInProgress.tag = resolveLazyComponentTag(Component);
  var resolvedProps = resolveDefaultProps(Component, props);
  var child;

  // code..。
  // 这里的处理和其他组件首次挂载时一致,比如FunctionCompoent, ClassCmp

  throw new Error("Element type is invalid. Received a promise that resolves to: " + Component + ". " + ("Lazy element type must resolve to a class or function." + hint));
}


var Uninitialized = -1;
var Pending = 0;
var Resolved = 1;
var Rejected = 2;

function lazyInitializer(payload) {
  if (payload._status === Uninitialized) {
    var ctor = payload._result;
    var thenable = ctor(); // 执行(function ctor() => import(http://Component));

    thenable.then(function (moduleObject) {
      // 此时的paylad和LazyCompoent仍有关联,属于引用关系
      // 所以在这里修改以后,cmp一样,等下次进来的时候就是moduleObject
      // 非常的巧妙
      if (payload._status === Pending || payload._status === Uninitialized) {
        var resolved = payload;
        resolved._status = Resolved;
        resolved._result = moduleObject;
      }
    }, function (error) {
         // errors...
    });

    if (payload._status === Uninitialized) {
      // 未返回的promise
      var pending = payload;
      pending._status = Pending;
      pending._result = thenable;
    }
  }

  if (payload._status === Resolved) {
    var moduleObject = payload._result;
      // dev import返回的对象,如果是默认到处就是default否则呢就是对象
    return moduleObject.default;
  } else {
    // throw 这个promise,被捕获被suspense处理
    // 如果没有被suspense包裹就会报错
    throw payload._result;
  }
}

// lazy
// lazt函数是React提供的,就是返回一个对象
function lazy(ctor) {
  var payload = {
    // We use these fields to store the result.
    _status: Uninitialized,
    _result: ctor
  };
  var lazyType = {
    $$typeof: REACT_LAZY_TYPE,
    _payload: payload,
    _init: lazyInitializer
  };
  // dev code...
  return lazyType;
}