React16源码: React中详解在渲染阶段Suspend的源码实现

React 之 Suspense 进行异步网络请求,然后再拿到请求后的数据进行渲染是很常见的需求,但这不可避免的需要先渲染一次没有数据的页面,数据返回后再去重新渲染。组件来渲染子组件,这个组件的特性是能根据传入 mode 来控制子组件样式的显隐,这有一个好处,就是能保存组件的状态,有些许类似于 Vue 的。至此,Suspense 的一生我们粗略的过完了,在源码中对 Suspense 的处理非常多,涉及到优先级相关的本篇都略过。通过其需要的 mode 参数值,我们可以大胆的猜测,应该是一个能控制是否显示子组件的组件,如果。 阅读详情

Suspend 挂起详解


1 )概述

  • 在react的更新过程当中,它的任务是可以被挂起的,也就是 Suspend
  • 关于 Suspend
    • 字面意思就是挂起
    • 在某次更新的任务更新完成之后,暂时不提交
      • 在 react更新中,分为两个阶段,首先是render阶段
        • 主要就是包含 performUnitOfWork 以及 completeUnitOfWork
        • 对拿到的 reactElement 进行一个向下一层一层渲染
        • 这个过程呢叫做 beginWork, 在这个过程中
        • 从一个root节点开始渲染渲染到某一层的最终的一个子节点之后
        • 然后再由这个子节点往上返回,并且渲染它的兄弟节点
        • 最终回到root这个过程,然后把一个 reactElement 形成的树最终渲染成一棵完整的fiber树
        • 在这个过程当中,会根据传入的props以及每个节点的不同的类型来给它创建不同的实例
        • 以及进行一些 diff 的内容, 这个过程为什么要叫 render 阶段呢?
        • 因为它完全不会影响我们目前的整个 dom 树,它不会更新任何 dom 节点的特性
        • 在更新的过程当中,会形成一个effect的链
        • 这些effect的链就代表着在真正commit的阶段是要去把哪些dom节点进行一个更新
      • 在 render 阶段完成了之后,就进入commit阶段
        • 把render阶段能够发现的所有需要更新的节点提交到dom的更新上面,来完成一个UI的更新
        • 在 suspend 当中,完成了 render 阶段的所有任务, 但是暂时把这个任务停在 render 阶段不提交
        • 也就是把最终要修改 dom 的任务给它停掉,就不去做这个事情
        • 那么这个更新过程就叫做被 Suspend 的,也就是被挂起了
    • 这个更新可能在下次更新中再次被执行

2 )更新过程回顾

一开始

    root
     |
     |  current
     ↓
   RootFiber    -----alternate----->   workInProgress
     |                                       |
     |                                       |
     ↓                                       ↓
  childFiber                            childWIP (childFiber 的 workInProgress)
  • 在执行更新的过程当中,一开始有一个root节点,这个root节点它是一个fiber对象
  • 它有一个属性叫 current 指向了 RootFiber
  • 这个 RootFiber 在更新完一次之后,它会有一个childFiber
  • 在要执行一个更新的过程当中,会把 rootFiber 去创建一个叫做 workInProgress 这么一个对象
  • 通过这个fiber对象的 alternate 属性去指向这个 workInProgress
  • 这个 workInProgress 跟 RootFiber 是两个完全不同的对象,只不过它们对象里面的有一些引用的属性是一样的
  • 比如说 memorizedState,还有 memorizedProps 这些常用的属性,然后在整个更新的过程当中
  • 都会通过 workInProgress 去创建它的 childFiber 的 workInProgress
  • 也就是说目前的 root 的 current 指向的这个 RouterFiber 以及它的childFiber
  • 跟在更新过程当中使用的 workInProgress 和 它的 childFiber 的 workInProgress 都是新的对象,不会相互影响

更新完成之后

    root
          \
           \
            \
             \
              \
               \
                \
                 \
                  \
                   \
                    \
                     \ current
                      \
                       \
                        \
                         \
                          \
                           \
                            \
                             \ 
                              \ 
                               \ 
                                \ 
                                 \ 
                                  ↘
   RootFiber  -----alternate----->  workInProgress
     |                                       |
     |                                       |
     ↓                                       ↓
  childFiber                            childWIP (childFiber 的 workInProgress)
  • 在整个更新完成之后,在执行了 commitRoot 之后,会做一个操作,叫做把 root.current 的指向变成 workInProgress
  • 这个时候因为已经把 workInProgress 上收集到的所有更新提交到 dom 上面了
  • 提交到 dom 上面之后,这个 workInProgress 跟 dom 的对应关系才是一一对应的
  • 而之前的rootFiber已经是一个过时的版本了, 如果当前的 root.current 还指向它的话,跟我们实际的dom的对应关系就不对了
  • 所以这时候,root 就直接修改它的 current 指向 workInProgress,就可以变成一个最新的状态
  • 这个 rootFiber 还会依然存在,存在于 workInProgress.alternate 上面
  • 后续要去继续更新的时候,又会从 workInProgress 上创建一个新的 workInProgress
  • 因为旧的 workInProgress 现在已经变成 root.current了,这个操作在哪里做呢?
    • 在commitRoot里面,完成了第二次commit,也就是去 commitAllLifeCycles 修改了所有dom的更新之后
    • 它会执行一句代码,叫做 root.current = finishedWork (packages/react-reconciler/src/ReactFiberScheduler.js#L730)
    • 这个 finishedWork 就是传入 commitRoot 的时候
    • 在render阶段更新完成的那个 workInProgress 对象
    • 需要注意它们两个对象 workInProgress 和 current,虽然是大部分属性都是一样的
    • 它们最大的区别就是两个完全独立的对象
    • 修改了 root.current 的指向之后,就代表 workInProgress 它的更新已经被提交了
    • 然后变成了一个新的状态,就存在 root.current 上面
  • 这就是在 react当中,有一个叫做 double buffer 的一个概念
  • 在更新完成之后,会修改root的指向来复用我们的workInProgress对象

3 )详解 Suspend

  • 这个过程完成之后,看下 Suspend
  • 在之前的 renderRoot, 在 workLoop 执行完成之后,会执行一堆判断

定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L1381

// Yield back to main thread.
if (didFatal) {
  const didCompleteRoot = false;
  stopWorkLoopTimer(interruptedBy, didCompleteRoot);
  interruptedBy = null;
  // There was a fatal error.
  if (__DEV__) {
    resetStackAfterFatalErrorInDev();
  }
  // `nextRoot` points to the in-progress root. A non-null value indicates
  // that we're in the middle of an async render. Set it to null to indicate
  // there's no more work to be done in the current batch.
  nextRoot = null;
  onFatal(root);
  return;
}

if (nextUnitOfWork !== null) {
  // There's still remaining async work in this tree, but we ran out of time
  // in the current frame. Yield back to the renderer. Unless we're
  // interrupted by a higher priority update, we'll continue later from where
  // we left off.
  const didCompleteRoot = false;
  stopWorkLoopTimer(interruptedBy, didCompleteRoot);
  interruptedBy = null;
  onYield(root);
  return;
}

// We completed the whole tree.
const didCompleteRoot = true;
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
const rootWorkInProgress = root.current.alternate;
invariant(
  rootWorkInProgress !== null,
  'Finished root should have a work-in-progress. This error is likely ' +
    'caused by a bug in React. Please file an issue.',
);

// `nextRoot` points to the in-progress root. A non-null value indicates
// that we're in the middle of an async render. Set it to null to indicate
// there's no more work to be done in the current batch.
nextRoot = null;
interruptedBy = null;

if (nextRenderDidError) {
  // There was an error
  if (hasLowerPriorityWork(root, expirationTime)) {
    // There's lower priority work. If so, it may have the effect of fixing
    // the exception that was just thrown. Exit without committing. This is
    // similar to a suspend, but without a timeout because we're not waiting
    // for a promise to resolve. React will restart at the lower
    // priority level.
    markSuspendedPriorityLevel(root, expirationTime);
    const suspendedExpirationTime = expirationTime;
    const rootExpirationTime = root.expirationTime;
    onSuspend(
      root,
      rootWorkInProgress,
      suspendedExpirationTime,
      rootExpirationTime,
      -1, // Indicates no timeout
    );
    return;
  } else if (
    // There's no lower priority work, but we're rendering asynchronously.
    // Synchronsouly attempt to render the same level one more time. This is
    // similar to a suspend, but without a timeout because we're not waiting
    // for a promise to resolve.
    !root.didError &&
    isYieldy
  ) {
    root.didError = true;
    const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime);
    const rootExpirationTime = (root.expirationTime = Sync);
    onSuspend(
      root,
      rootWorkInProgress,
      suspendedExpirationTime,
      rootExpirationTime,
      -1, // Indicates no timeout
    );
    return;
  }
}

if (isYieldy && nextLatestAbsoluteTimeoutMs !== -1) {
  // The tree was suspended.
  const suspendedExpirationTime = expirationTime;
  markSuspendedPriorityLevel(root, suspendedExpirationTime);

  // Find the earliest uncommitted expiration time in the tree, including
  // work that is suspended. The timeout threshold cannot be longer than
  // the overall expiration.
  const earliestExpirationTime = findEarliestOutstandingPriorityLevel(
    root,
    expirationTime,
  );
  const earliestExpirationTimeMs = expirationTimeToMs(earliestExpirationTime);
  if (earliestExpirationTimeMs < nextLatestAbsoluteTimeoutMs) {
    nextLatestAbsoluteTimeoutMs = earliestExpirationTimeMs;
  }

  // Subtract the current time from the absolute timeout to get the number
  // of milliseconds until the timeout. In other words, convert an absolute
  // timestamp to a relative time. This is the value that is passed
  // to `setTimeout`.
  const currentTimeMs = expirationTimeToMs(requestCurrentTime());
  let msUntilTimeout = nextLatestAbsoluteTimeoutMs - currentTimeMs;
  msUntilTimeout = msUntilTimeout < 0 ? 0 : msUntilTimeout;

  // TODO: Account for the Just Noticeable Difference

  const rootExpirationTime = root.expirationTime;
  onSuspend(
    root,
    rootWorkInProgress,
    suspendedExpirationTime,
    rootExpirationTime,
    msUntilTimeout,
  );
  return;
}

// Ready to commit.
onComplete(root, rootWorkInProgress, expirationTime);
  • 比如说 if (didFatal) {},就是说有致命错误的时候,会执行 onFatal

    if (didFatal) {
      const didCompleteRoot = false;
      stopWorkLoopTimer(interruptedBy, didCompleteRoot);
      interruptedBy = null;
      // There was a fatal error.
      if (__DEV__) {
        resetStackAfterFatalErrorInDev();
      }
      // `nextRoot` points to the in-progress root. A non-null value indicates
      // that we're in the middle of an async render. Set it to null to indicate
      // there's no more work to be done in the current batch.
      nextRoot = null;
      onFatal(root);
      return;
    }
    
    • onFatal 是给 root.finishedWork,给它直接设为null,那么这个不算
  • 还有就是 if (nextUnitOfWork !== null) {}

    if (nextUnitOfWork !== null) {
      // There's still remaining async work in this tree, but we ran out of time
      // in the current frame. Yield back to the renderer. Unless we're
      // interrupted by a higher priority update, we'll continue later from where
      // we left off.
      const didCompleteRoot = false;
      stopWorkLoopTimer(interruptedBy, didCompleteRoot);
      interruptedBy = null;
      onYield(root);
      return;
    }
    
    • 这个情况是说这个更新过程是通过 reactScheduler 它进行时间片的更新的一个过程
    • 而这个root的更新又因为比较的长,一个时间片没更新完, 这个时候跳出更新的时候,它就是 onYield
    • 它下一次等浏览器执行完动画之类的操作之后,有新的一个时间片进来的时候,我们会继续在 nextUnitOfWork 上面进行一个更新
    • 这就是类似于中断,再继续的一个流程,这个跟 suspend 其实也没有什么关系
    • 真正跟 suspend 有关的是下面几个
  • if(nextRenderDidError),会把提交放到第一优先级的任务上

    if (nextRenderDidError) {
      // There was an error
      if (hasLowerPriorityWork(root, expirationTime)) {
        // There's lower priority work. If so, it may have the effect of fixing
        // the exception that was just thrown. Exit without committing. This is
        // similar to a suspend, but without a timeout because we're not waiting
        // for a promise to resolve. React will restart at the lower
        // priority level.
        markSuspendedPriorityLevel(root, expirationTime);
        const suspendedExpirationTime = expirationTime;
        const rootExpirationTime = root.expirationTime;
        onSuspend(
          root,
          rootWorkInProgress,
          suspendedExpirationTime,
          rootExpirationTime,
          -1, // Indicates no timeout
        );
        return;
      } else if (
        // There's no lower priority work, but we're rendering asynchronously.
        // Synchronsouly attempt to render the same level one more time. This is
        // similar to a suspend, but without a timeout because we're not waiting
        // for a promise to resolve.
        !root.didError &&
        isYieldy
      ) {
        root.didError = true;
        const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime);
        const rootExpirationTime = (root.expirationTime = Sync);
        onSuspend(
          root,
          rootWorkInProgress,
          suspendedExpirationTime,
          rootExpirationTime,
          -1, // Indicates no timeout
        );
        return;
      }
    }
    
    • 也就是说, 这边有一个判断 if(hasLowerPriorityWork) {}, 如果它是有一个低优先级的任务,还没有被更新的
    • 这个时候调用了 markSuspendedPriorityLevel,这代表要把当前的这一次更新的内容去给它 suspend 了
    • 然后标记这个更新被 suspend了, 它会调用一个叫做 onSuspend 的方法
      // packages/react-reconciler/src/ReactFiberScheduler.js#L1954
      function onSuspend(
        root: FiberRoot,
        finishedWork: Fiber,
        suspendedExpirationTime: ExpirationTime,
        rootExpirationTime: ExpirationTime,
        msUntilTimeout: number,
      ): void {
        root.expirationTime = rootExpirationTime;
        // !shouldYieldToRenderer() 表示任务还没有超时
        // 并且 msUntilTimeout === 0 直接设置了 finishedwork
        // 这个时候最终会直接调用 commitRoot
        if (msUntilTimeout === 0 && !shouldYieldToRenderer()) {
          // Don't wait an additional tick. Commit the tree immediately.
          root.pendingCommitExpirationTime = suspendedExpirationTime;
          root.finishedWork = finishedWork;
        } else if (msUntilTimeout > 0) {
          // Wait `msUntilTimeout` milliseconds before committing.
          // 这个 scheduleTimeout 就是 window.setTimeout 方法
          root.timeoutHandle = scheduleTimeout(
            onTimeout.bind(null, root, finishedWork, suspendedExpirationTime),
            msUntilTimeout,
          );
        }
      }
      
      • 进入 onTimeout
        function onTimeout(root, finishedWork, suspendedExpirationTime) {
          // The root timed out. Commit it.
          root.pendingCommitExpirationTime = suspendedExpirationTime;
          root.finishedWork = finishedWork; // 注意,这里
          // Read the current time before entering the commit phase. We can be
          // certain this won't cause tearing related to batching of event updates
          // because we're at the top of a timer event.
          recomputeCurrentRendererTime();
          currentSchedulerTime = currentRendererTime;
          flushRoot(root, suspendedExpirationTime); // flushRoot 强制调用 commitRoot
        }
        
        • 进入 flushRoot
          function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
            invariant(
              !isRendering,
              'work.commit(): Cannot commit while already rendering. This likely ' +
                'means you attempted to commit from inside a lifecycle method.',
            );
            // Perform work on root as if the given expiration time is the current time.
            // This has the effect of synchronously flushing all work up to and
            // including the given time.
            nextFlushedRoot = root;
            nextFlushedExpirationTime = expirationTime;
            performWorkOnRoot(root, expirationTime, false); // 在这里面,存在 finishedWork 则直接调用 `completeRoot`
            // Flush any sync work that was scheduled by lifecycles
            performSyncWork();
          }
          
          • 是否要调用completeRoot, 都是通过判断 root.finishedWork 来完成的
          • 只要 root 它有已经完成的更新的工作,那么它就要去调用 commitRoot 来进行一个commit
          • 而通过给 root.finishedWork设置成null,就是告诉后续的代码,没有任务要提交
          • 所以不会执行 completeRoot, 所以不会 commitRoot
      • 看到这个方法,它只是设置了 root.expirationTime = rootExpirationTime
      • 然后接下去, 因为传进来的 msUnitlTimeout 是 -1,所以接下去这两个判断是都不符合的
      • 也就是说它接下去什么任务都没有做,而且它也没有去调用 commitRoot
      • 那么这个更新流程不就是被白费了吗?事实上也确实是如此的, 这边回头看 react 上写的相关注释
        // There's lower priority work. If so, it may have the effect of fixing
        // the exception that was just thrown. Exit without committing. This is
        // similar to a suspend, but without a timeout because we're not waiting
        // for a promise to resolve. React will restart at the lower
        // priority level.
        
      • 如果这边有低优先级的任务,就不提交这次更新,因为没有提交这个更新
      • 而且在 current 上面它的 updateQueen 是没有被删除的
      • 也就是说这些 update 它依然存在, 这些 update 存在的话
      • 在下一次低优先级的任务去执行更新的时候,它依然会执行update
      • 这个时候它有可能可以修复在这一次渲染当中出现的问题
      • 所以只要在之前的渲染当中出现了错误,而且有低优先级的任务在
      • react会直接不提交,而是把这个提交的更新放到低优先级的任务上,再去渲染一次
  • 如果没有低优先级的任务,react 会直接发起一个新的同步更新

  • 就是在 else if 下面, 这边的判断条件是比较苛刻的 else if(!root.didError && isYieldy) {}

    else if (
      // There's no lower priority work, but we're rendering asynchronously.
      // Synchronsouly attempt to render the same level one more time. This is
      // similar to a suspend, but without a timeout because we're not waiting
      // for a promise to resolve.
      !root.didError &&
      isYieldy
    ) {
      root.didError = true;
      const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime);
      const rootExpirationTime = (root.expirationTime = Sync);
      onSuspend(
        root,
        rootWorkInProgress,
        suspendedExpirationTime,
        rootExpirationTime,
        -1, // Indicates no timeout
      );
      return;
    }
    
    • 在没有错误和存在需要被中断和恢复的任务的条件下,可以再去发起一次,
    • 如果符合这个条件,会设置 root.expirationTime = (root.exirationTime = Sync)
    • 下一次更新的过程,会走 Sync 的流程
    • 不会走时间片更新,而会强制进行一个同步的直接更新以及渲染的过程
    • 同时,这边又设置了 root.nextExpirationTimeToWorkOn = expirationTime
    • 这个 expirationTime 就是这一次 renderRoot 的时候,它的 nextExpirationTimeToWorkOn
    • 然后它调用了 onSuspend, 这里注意传入的是 -1,所以不会做任何的事情
    • 为何要设置 root.expirationTime = (root.exirationTime = Sync)
      • 在 performWork 里面,在执行 performWorkOnRoot, 在这个函数里面会有这么一段
        renderRoot(root, isYieldy); // 这边调用 renderRoot 返回了
        finishedWork = root.finishedWork;
        // 如果没有 finishedWork 就不会执行 completeRoot
        if (finishedWork !== null) {
          // We've completed the root. Commit it.
          completeRoot(root, finishedWork, expirationTime);
        }
        
      • 在 performWork 里面,在执行完 performWorkOnRoot 之后, 会有这么一段
        performWorkOnRoot(
          nextFlushedRoot,
          nextFlushedExpirationTime,
          currentRendererTime > nextFlushedExpirationTime,
        );
        findHighestPriorityRoot();
        
      • 执行 findHighestPriorityRoot 会去再次找一次优先级最高的 root
      • 找优先级最高的root这个方法,是根据 expirationTime 的大小来进行一个判断的
      • 在这个过程当中,Sync 的 expirationTime 的优先级是最高的
      • 而在 renderRoot 里面设置了这个 root.exirationTime = Sync
      • 说明这个 root 会立马就被执行一个更新, 因为那边的循环它是没有结束的
      • 所以这边强制发起了一次对这个 nextExpirationTimeToWorkOn
      • 优先级的任务进行一次同步的更新这么一个操作,来强制再次进行更新
      • 通过这样来重新渲染一次,看一下是否在重新渲染的过程当中能够解决前一次渲染当中出现的这个错误
      • 一开始 root.didError 肯定是 false,所以这边是可以符合条件的
      • 这边在发起同步过程中,设置它的 didError 为 true
      • 这样的话,如果这一次同步更新,它依然没有完成任务的话,再后来是进不来这个判断的,它还是会被强制提交的
      • 这两种的任务都是被挂起的, 而且可以发现的是,被挂起的任务是根本不会走 commitRoot 的流程的
      • 也就是说这个 render 更新流程直接被抛弃了,我们的 workInProgress 已经没有用了
      • 因为下一次要重新进行render的时候,我们的 workInProgress 也就是 nextUnitOfWork 是会通过 nextRoot.current 来进行创建的
        // packages/react-reconciler/src/ReactFiberScheduler.js#L1230
        nextUnitOfWork = createWorkInProgress(
          nextRoot.current,
          null,
          nextRenderExpirationTime,
        );
        
      • 这个时候因为直接 onSuspend 了,没有执行commit
      • 这个时候 root.current 指针是没有改变的
      • 所以它还会从老的状态上重新发起一次更新,这就是 onSuspend 任务挂起它的一个意思
  • 对于Suspend 的来说,最大的一个情况,就是在下面这个情况下面, if (!isExpired && nextLatestAbsoluteTimeoutMs !== -1) {}

    if (isYieldy && nextLatestAbsoluteTimeoutMs !== -1) {
      // The tree was suspended.
      const suspendedExpirationTime = expirationTime;
      markSuspendedPriorityLevel(root, suspendedExpirationTime);
    
      // Find the earliest uncommitted expiration time in the tree, including
      // work that is suspended. The timeout threshold cannot be longer than
      // the overall expiration.
      const earliestExpirationTime = findEarliestOutstandingPriorityLevel(
        root,
        expirationTime,
      );
      const earliestExpirationTimeMs = expirationTimeToMs(earliestExpirationTime);
      if (earliestExpirationTimeMs < nextLatestAbsoluteTimeoutMs) {
        nextLatestAbsoluteTimeoutMs = earliestExpirationTimeMs;
      }
    
      // Subtract the current time from the absolute timeout to get the number
      // of milliseconds until the timeout. In other words, convert an absolute
      // timestamp to a relative time. This is the value that is passed
      // to `setTimeout`.
      const currentTimeMs = expirationTimeToMs(requestCurrentTime());
      let msUntilTimeout = nextLatestAbsoluteTimeoutMs - currentTimeMs;
      msUntilTimeout = msUntilTimeout < 0 ? 0 : msUntilTimeout;
    
      // TODO: Account for the Just Noticeable Difference
    
      const rootExpirationTime = root.expirationTime;
      onSuspend(
        root,
        rootWorkInProgress,
        suspendedExpirationTime,
        rootExpirationTime,
        msUntilTimeout,
      );
      return;
    }
    
    • 这边会执行一个 onSuspend,并且会传入一个 msUntilTimeout
    • 就这个 timeoutout,可以看上面这个条件,msUntilTimeout = msUntilTimeout < 0 ? 0 : msUntilTimeout
  • 如果以上条件都不满足,不再发起新的同步更新,就会直接走到后面准备进入commit阶段

    // Ready to commit.
    onComplete(root, rootWorkInProgress, expirationTime);
    
    • 调用 onComplete, 而 onComplete,就会调用 commitRoot

总结3种 Suspend 的场景

  • 1 )把优先级放到低优先级的任务上
    • 会把当前 render 直接废弃,让低优先级的任务
    • 再次去渲染这些更新来查看他是否可以把错误解决掉
  • 2 )直接发起一个新的同步更新
    • 没有低优先级的任务,会重新发起的是同步更新来强制再次去渲染一次来看是否可以解决这个问题
    • 如果不能解决,那么就代表不能解决这个问题,只能按照错误的方式去把内容渲染出来
  • 3 )设置timeout然后提交
    • 只有在 throw 的是 Promise 的情况
    • 也就是通过 Suspense 这个功能去实现 throw 一个 Promise
    • 然后等到 Promise 解决之后再去渲染新的内容的一个情况
    • 这种情况会设置 timeout,这里先跳过
AMBA-ATB spec AMBA-ATB spec 1. ATB总线的用途及版本 1.1 ATB总线用途 一般来说,ATB(Advanced Trace Bus)总线用来在ARM的coresight组件之间传递trace数据。如下图所示: 1.2 ATB总线的版本 ATB(Advanced Trace Bus)共有两个版本,其中v1.0作为AMBA3的一部分,于2006年发布的,而v1.1则是2012年随着AMBA4发布的。ATB规范新增了数据(主要针对trace data)不可知的接口,以便在跟踪系统中根据 AMBA 规范跟 阅读详情

相关推荐

RK3588平台-ES8388音频驱动调试与设备树配置实战

本文详细解析了在RK3588平台上调试ES8388音频驱动的实战过程。内容涵盖Linux音频驱动的三层架构(Machine、Platform、Codec),并重点提供了设备树(DTS)的完整配置示例,包括I2C节点、声卡节点及音频路由设置。文章还分享了驱动加载验证、amixer调试命令以及常见问题(如无声、变调)的排查思路,旨在帮助开发者快速完成RK3588与ES8388的音频系统集成。

mango的博客 809

react-Suspense工作原理分析

之所以是将 Suspense 放在最后来分析,是因为对 Suspense 的处理涉及到多个状态,这些状态在之前的步骤中或许会被修改,因此在了解其他步骤之后再来看 Suspense 或许更容易理解。从上面的描述即可以看出,Suspense 是一个加载数据的标准,lazy 只是该标准下实现的一个工具方法。beginWork - second pass,由于此时 DidCapture 存在,会将 primary 组件作为子节点,并将 fallback 组件作为 primary 组件的兄弟节点。

grooyo的博客 961

德国冯哈勃 Faulhaber2342L012空心杯减速电机带编码器

为了制作平衡车,我在淘宝上面买了一个 Faulhaber2342L012空心杯减速电机电机,具体参数如下: 一:电机参数 型    号:德国冯哈勃Faulhaber带编码器空心杯减速电机2342L012  12V每分 钟 120转 工作电压: 12V 空载转速:8100RPM(转每分钟) 减速后速:120RPM(转每分钟) 直     径:30mm

u011511430的专栏 1万+

React中遇到的渲染死循环问题

刚开始学react没多久,现在边干活边学习,今天遇到了一个bug,百度了一下解决了,总结一下   当我写下面这段代码的时候,遇到了这个bug,f12之后发现,无限报警告 代码:&lt;a href = "#" onClick={this.handleShow(true)}&gt;显示&lt;/a&gt; 警告: .Warning: setState(...): Cannot update ...

qq_39674542的博客 7923

从交换机安全配置,看常见局域网攻击

前言 构建零信任网络,自然离不开网络准入(NAC),这就涉及到交换机的一些安全测试,于是有了此文《从交换机安全配置看常见局域网攻击》。 交换机安全配置 如本文标题所说从交换机安全配置看常见的局域网攻击,那么下面提到的各种攻击方式,自然是建立在交换机安全配置之上来说的。我们通过h3c官方提供的《H3C 交换机安全加固手册》和《安全配置指导》,从这两份手册可以看出交换机支持的安全配置非常多,基本已经覆盖了各种我们熟知局域网攻击。下面就挑几个典型的攻击方式进行演示。 arp欺骗 【一>所有资源获取&lt

kali_Ma的博客 1万+

【数据挖掘】心跳信号分类预测 之 特征工程 —— 学习笔记(三)

三、特征工程 3.1 看书 常言道,“巧妇难为无米之炊”。在 ML 中,数据和特征将作“米”,模型和算法则为“妇”。若无数量充足的数据、良好表示的特征,再强大的模型也难以得到满意的结果 (garbage in,garbage out)。对于 ML 问题,数据和特征往往决定了结果的上限,而模型、算法的选择及优化则是在逐步接近该上限。 特征工程,顾名思义,是对原始数据进行一系列工程处理,将其提炼为特征,作为输入供算法和模型使用。在本质上,特征工程是一个表示和展现数据的过程。在实际中,特征工程旨在去除...

闻韶 3359

Suspense

这个是为了 懒加载 React Suspense让我们轻松定义延迟加载的组件。 为什么会出现这种情况呢? ---》 因为在路由中使用的懒加载路由方式 所以说 难免会有点慢 如果没有这个处理 那么就会出现一段时间的白屏 用户体验较差 const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <div> <Su

大路宽宽的博客 589

LoRa模块选型指南:正点原子ATK-LORA-01与SX1278芯片深度评测(含实测距离/功耗数据)

本文深度评测了正点原子ATK-LORA-01与SX1278芯片的性能表现,通过实测数据展示其在复杂环境中的通信距离与功耗表现。特别适合智慧农业、工业监测等物联网应用场景,提供详细的配置指南和优化技巧,帮助开发者实现高效、低功耗的LoRa通信解决方案。

weixin_29191081的博客 291

React16源码: Suspense与lazy源码实现

Suspense 是在 react16.6 提供的新 feature,用于加载的缓冲的内置组件

Wang的专栏 988

保姆级教程:用CST 2023一步步复现一个1-8GHz Vivaldi天线(含参数优化与结果分析)

本文提供了一份详细的CST 2023教程,指导读者如何从零开始设计并优化1-8GHz Vivaldi天线。内容涵盖工程设置、参数化建模、仿真加速技巧以及性能优化策略,特别适合需要实现宽带天线设计的工程师和研究人员。通过实战案例,帮助用户掌握Vivaldi天线的核心设计要点和CST高级功能应用。

weixin_30800987的博客 619

图像处理系列——直方图之直方图规定化(Histogram Specification)

直方图规定化也叫直方图匹配(Histogram Matching),让原图像的像素分布与规定图像的像素分布保持一致。直方图规定化比直方图均衡化更加灵活,算是直方图均衡的变种。常用于图片风格一致性的自动处理,能有效的改善图像的整体或局部的特征,广泛运用在图像增强处理中。

nanke_yh的博客 4595

react-konva 完全指南:用 React 声明式构建 Konva 2D 画布交互应用

本文以 [react-konva](https://link.gitcode.com/i/1750038db3a3c4faf780196cfde09209) 官方 README 为主线,系统讲解如何用 React 的声明式 JSX 语法驱动 Konva 2D 画布库,覆盖安装、组件与事件 API、ref 引用、严格模式、最小化打包、Next.js 集成、Context 桥接以及与 react-ca

gitblog_01193的博客 981

【ArcGIS遇上Python】ArcGIS10.8 Python代码批量完美实现MODIS NDVI数据格式转换和投影变换

由于论文的需要,将MODIS NDVI数据进行投影变换和格式转换,具体操作可以参照:《ArcGIS10.8完美实现MODIS NDVI数据格式转换和投影变换》,但是该文章中的做法只能一次性实现一景影像的转换,没法批量,虽然ArcGIS中提供了Batch的方法但是需要挨个添加数据,确定输出路径等等,本文就实现以ArcGIS10.8 Python代码批量完美实现MODIS NDVI数据格式转换和投影变换。先来看投影后的效果: 在实现批量投影转换之前,需要两个投影文件:Sinusoidal.prj和MyAl

智慧空间实验室 3816

React 专家级面试指南:从并发渲染到性能优化与测试的完整解析(preguntas-entrevista-react 深度解读)

React 面试的专家级问题往往不再问"怎么用",而是追问"为什么"和"底层如何工作"。本文基于开源仓库 preguntas-entrevista-react 的专家章节 [manuscript/03-experto.md](https://link.gitcode.com/i/c6c626fdfefa861f80a6fbcc2a78e786) 整理,覆盖 26 个高频专家题:从"React 到底

gitblog_01061的博客 86

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Langfuse 前端性能优化实战:用战略性的 Suspense 边界实现更快的首屏渲染

在 Langfuse 的 Web 前端(基于 React 19.2.4 与 Next.js 16.3.3,见 [web/package.json](https://link.gitcode.com/i/878c11ffb0f5f44987e4f1c566252b28))中,页面加载体验直接关系到 AI 可观测性数据的浏览效率。本文围绕 Vercel Engineering 维护的规则文档 [asy

gitblog_00370的博客 210

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Relay 加载状态指南:使用 React Suspense 与 usePreloadedQuery / useLazyLoadQuery 实现数据驱动的 Loading UI

在 Relay 应用中,`usePreloadedQuery` 与 `useLazyLoadQuery` 会在查询数据尚未就绪时"暂停"渲染,本文围绕 [website/versioned_docs/version-v16.0.0/guided-tour/rendering/loading-states.md](https://link.gitcode.com/i/cd7deb1ab5e7c914

gitblog_00194的博客 585

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

React 现代化改造实战指南:版本升级、Class 迁移 Hooks 与 React 18 并发特性

本篇技术指南以 agents24/agents 仓库中 `framework-migration` 插件所附带的 `react-modernization` 技能为骨架,系统讲解 React 应用的现代化改造路径:从 React 16→17→18 的版本升级与破坏性变更应对,到 Class 组件向 Hooks 的迁移、并发特性(Suspense、Transitions)的落地,以及 jscodes

gitblog_00152的博客 389

以不同数据重新加载查询:Relay 16 的两种 Refetching 实战方案

在 Relay 驱动的 React 应用中,"以不同数据重新加载查询"(Refetching Queries with Different Data)是切换当前选中项、切换数据列表或让已渲染内容过渡到新数据时的核心操作。本指南基于 [refetching-queries-with-different-data.md](https://link.gitcode.com/i/76efc8d9ea40a

gitblog_01141的博客 139

Relay 入门实战:从零搭建数据驱动的 React + GraphQL 应用(Step-by-Step Guide 全解析)

Relay 是一套用于管理和声明式获取 GraphQL 数据的 JavaScript 框架:开发者只需用 GraphQL 声明"每个组件需要什么数据",Relay 便会聚合这些依赖、以最少的网络往返高效地完成数据获取。本文基于仓库内 [step-by-step-guide.md](https://link.gitcode.com/i/4a4c04126482baef5ba55738dcc005f2

gitblog_00538的博客 671

Relay 错误状态处理指南:用 Error Boundary 构建可恢复的查询渲染体验

在 Relay 的 React 应用中,查询组件(如使用 `usePreloadedQuery` 或 `useLazyLoadQuery` 渲染数据的组件)在网络请求失败时,需要一种机制来捕获渲染过程中的错误并展示友好的错误 UI。本文基于 Relay 官方指南([error-states.md](https://link.gitcode.com/i/39464c45f96a9bd4d84cda3

gitblog_00085的博客 726

Relay 的 useLazyLoadQuery 完全指南:渲染期惰性查询、Fetch Policy 与 Suspense 实践

`useLazyLoadQuery` 是 React Relay 提供的 React Hook,用于在组件渲染期间获取并读取一条 GraphQL 查询的数据。本指南以 Relay v16 官方 API 文档为核心,结合仓库源码([useLazyLoadQuery.js](https://link.gitcode.com/i/6e9d8e6727305af338b00c66af845e65)、[Qu

gitblog_00561的博客 571

Relay Hooks 升级指南:从容器 API 迁移到基于 Hooks 的 React 数据获取方案

Relay Hooks 是 React Relay 官方推出的一组基于 React Hooks 的全新 API,用于替代早期基于高阶组件的容器(Container)式 API。本文以 Relay v15 文档体系为准,系统讲解如何启用 Relay Hooks、它与现有容器 API 的兼容关系,以及将 `QueryRenderer`、Fragment Container、Refetch Contai

gitblog_00019的博客 382

Relay 入门实战:从 Create React App 到 Suspense 数据预取的分步搭建指南

> 本文以 Relay 官方 `step-by-step-guide` 文档为骨架,完整呈现“从零创建 React 应用 → 用原生 fetch 请求 GraphQL → 引入 Relay 编译器与运行时 → 以 Relay Hooks + Suspense 声明式读取数据”的完整链路,并穿插仓库源码(如 [RelayNetwork.js](https://link.gitcode.com/i/9

gitblog_00901的博客 130

Meteor 3 迁移指南:React 前端如何拥抱异步 API 与 Suspense 版 Hooks

Meteor 3.0 将整个框架的同步 API 全面切换到基于 Promise 的异步模型,前端 React 代码的迁移也因此有了新的选择:你可以继续使用客户端同步的 MongoDB 方法,也可以改用 `meteor/react-meteor-data/suspense` 提供的 Suspendable Hooks 来消费异步方法,保持前后端同构(isomorphic)代码风格。本文将基于官方迁移

gitblog_00489的博客 469

用 Remotion 为 Elsa Workflows 生成 README 演示视频:elsa-readme-video 项目源码与命令实战

Elsa Workflows 是运行在 .NET 应用内部的工作流引擎(见 [README](https://link.gitcode.com/i/61d8d5e5966b9c62a8efb4a12fdc5972))。为了让仓库首页拥有一段直观、可自动再生的产品演示视频,项目在 `design/video/elsa-readme-video/` 下用 **Remotion**(React 驱动的程

gitblog_00472的博客 164

Relay 渲染 Connections 列表数据:usePaginationFragment 分页渲染完整指南

在 Relay 构建数据驱动的 React 应用时,GraphQL Connection 是对"列表数据"进行增量分页查询的标准方式。本篇指南以 Relay(GitHub 加速计划 / relay29 / relay)官方文档 v16.0.0 的《Rendering Connections》为核心,完整讲解如何在 fragment 中声明一个 connection 字段、如何通过 `usePagi

gitblog_00697的博客 460
上一篇: React16源码: React中event事件中batchedUpdates, controlled inputs以及事件绑定dispatchEvent源码实现
下一篇: React16源码: React中更新阶段中不同类型的expirationTime之pendingTime,suspendedTime以及pingedTime的源码实现
Wang's Blog
博客等级 码龄12年 7434粉丝 2421原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值