返回顶部
首页 > 资讯 > 前端开发 > JavaScript >reactSuspense工作原理解析
  • 343
分享到

reactSuspense工作原理解析

2024-04-02 19:04:59 343人浏览 独家记忆
摘要

目录Suspense 基本应用Suspense 原理基本流程源码解读 - primary 组件源码解读 - 异常捕获源码解读 - 添加 promise 回调源码解读-Suspense

Suspense 基本应用

Suspense 目前在 React 中一般配合 lazy 使用,当有一些组件需要动态加载(例如各种插件)时可以利用 lazy 方法来完成。其中 lazy 接受类型为 Promise<() => {default: ReactComponet}> 的参数,并将其包装为 react 组件。ReactComponet 可以是类组件函数组件或其他类型的组件,例如:

 const Lazy = React.lazy(() => import("./LazyComponent"))
 <Suspense fallback={"loading"}>
        <Lazy/> // lazy 包装的组件
 </Suspense>

由于 Lazy 往往是从远程加载,在加载完成之前 react 并不知道该如何渲染该组件。此时如果不显示任何内容,则会造成不好的用户体验。因此 Suspense 还有一个强制的参数为 fallback,表示 Lazy 组件加载的过程中应该显示什么内容。往往 fallback 会使用一个加载动画。当加载完成后,Suspense 就会将 fallback 切换为 Lazy 组件的内容。一个完整的例子如下:

function LazyComp(){
  console.info("sus", "render lazy")
  return "i am a lazy man"
}
function delay(ms){
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms)
  })
}
// 模拟动态加载组件
const Lazy = lazy(() => delay(5000).then(x => ({"default": LazyComp})))
function App() {
  const context = useContext(Context)
  console.info("outer context")
  return (
      <Suspense fallback={"loading"}>
        <Lazy/>
      </Suspense>
  )
}

这段代码定义了一个需要动态加载的 LazyComp 函数式组件。会在一开始显示 fallback 中的内容 loading,5s 后显示 i am a lazy man。

Suspense 原理

虽然说 Suspense 往往会配合 lazy 使用,但是 Suspense 是否只能配合 lazy 使用?lazy 是否又必须配合Suspense? 要搞清楚这两个问题,首先要明白 Suspense 以及 lazy 是在整个过程中扮演的角色,这里先给出一个简单的结论:

  • Suspense: 可以看做是 react 提供用了加载数据的一个标准,当加载到某个组件时,如果该组件本身或者组件需要的数据是未知的,需要动态加载,此时就可以使用 Suspense。Suspense 提供了加载 -> 过渡 -> 完成后切换这样一个标准的业务流程。
  • lazy: lazy 是在 Suspense 的标准下,实现的一个动态加载的组件的工具方法。

从上面的描述即可以看出,Suspense 是一个加载数据的标准,lazy 只是该标准下实现的一个工具方法。那么说明 Suspense 除配合了 lazy 还可以有其他应用场景。而 lazy 是 Suspense 标准下的一个工具方法,因此无法脱离 Suspense 使用。接下来通过 lazy + Suspense 方式来给大家分析具体原理,搞懂了这部分,我们利用 Suspense 实现自己的数据加载也不是难事。

基本流程

在深入了解细节之前,我们先了解一下 lazy + Suspense 的基本原理。这里需要一些 react 渲染流程的基本知识。为了统一,在后续将动态加载的组件称为 primary 组件,fallback 传入的组件称为 fallback 组件,与源码保持一致。

  • 当 react 在 beginWork 的过程中遇到一个 Suspense 组件时,会首先将 primary 组件作为其子节点,根据 react 的遍历算法,下一个遍历的组件就是未加载完成的 primary 组件。
  • 当遍历到 primary 组件时,primary 组件会抛出一个异常。该异常内容为组件 promise,react 捕获到异常后,发现其是一个 promise,会将其 then 方法添加一个回调函数,该回调函数的作用是触发 Suspense 组件的更新。并且将下一个需要遍历的元素重新设置为 Suspense,因此在一次 beginWork 中,Suspense 会被访问两次。
  • 又一次遍历到 Suspense,本次会将 primary 以及 fallback 都生成,并且关系如下:

虽然 primary 作为 Suspense 的直接子节点,但是 Suspense 会在 beginWork 阶段直接返回 fallback。使得直接跳过 primary 的遍历。因此此时 primary 必定没有加载完成,所以也没必要再遍历一次。本次渲染结束后,屏幕上会展示 fallback 的内容

  • 当 primary 组件加载完成后,会触发步骤 2 中 then,使得在 Suspense 上调度一个更新,由于此时加载已经完成,Suspense 会直接渲染加载完成的 primary 组件,并删除 fallback 组件。

这 4 个步骤看起来还是比较复杂。相对于普通的组件主要有两个不同的流程:

  • primary 会组件抛出异常,react 捕获异常后继续 beginWork 阶段。
  • 整个 beginWork 节点,Suspense 会被访问两次

不过基本逻辑还是比较简单,即是:

  • 抛出异常
  • react 捕获,添加回调
  • 展示 fallback
  • 加载完成,执行回调
  • 展示加载完成后的组件

整个 beginWork 遍历顺序为:

 Suspense -> primary -> Suspense -> fallback

源码解读 - primary 组件

整个 Suspend 的逻辑相对于普通流程实际上是从 primary 组件开始的,因此我们也从 react 是如何处理 primary 组件开始探索。找到 react 在 beginWork 中处理处理 primary 组件的逻辑的方法 mountLazyComponent,这里我摘出一段关键的代码:

  const props = workInProgress.pendingProps;
  const lazyComponent: LazyComponentType<any, any> = elementType;
  const payload = lazyComponent._payload;
  const init = lazyComponent._init;
  let Component = init(payload); // 如果未加载完成,则会抛出异常,否则会返回加载完成的组件

其中最关键的部分莫过于这个 init 方法,执行到这个方法时,如果没有加载完成就会抛出 Promise 的异常。如果加载完成就直接返回完成后的组件。我们可以看到这个 init 方法实际上是挂载到 lazyComponent._init 方法,lazyComponent 则就是 React.lazy() 返回的组件。我们找到 React.lazy() :

export function lazy<T>(
  ctor: () => Thenable<{default: T, ...}>,
): LazyComponent<T, Payload<T>> {
  const payload: Payload<T> = {
    // We use these fields to store the result.
    _status: Uninitialized,
    _result: ctor,
  };
  const lazyType: LazyComponent<T, Payload<T>> = {
    $$typeof: REACT_LAZY_TYPE,
    _payload: payload,
    _init: lazyInitializer,
  };

这里的 lazyType 实际上就是上面的 lazyComponent。那么这里的 _init 实际上来自于另一个函数 lazyInitializer:

function lazyInitializer<T>(payload: Payload<T>): T {
  if (payload._status === Uninitialized) {
    console.info("sus", "payload status", "Uninitialized")
    const ctor = payload._result;
    const thenable = ctor(); // 这里的 ctor 就是我们返回 promise 的函数,执行之后得到一个加载组件的 promise
    // 加载完成后修改状态,并将结果挂载到 _result 上
    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;
    console.info("sus", "get lazy resolved result")
    return moduleObject.default; // 注意这里返回的是 moduleObject.default 而不是直接返回 moduleObject
  } else {
    // 否则抛出异常
    console.info("sus, raise a promise", payload._result)
    throw payload._result;
  }
}

因此执行这个方法大致可以分为两个状态:

  • 未加载完成时抛出异常
  • 加载完成后返回组件

到这里,整个 primary 的逻辑就搞清楚了。下一步则是搞清楚 react 是如何捕获并且处理异常的。

源码解读 - 异常捕获

react 协调整个阶段都在 workLoop 中执行,代码如下:

  do {
    try {
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);

可以看到 catch 了 error 后,整个处理过程在 handleError 中完成。当然,如果是如果 primary 组件抛出的异常,这里的 thrownValue 就为一个 priomise。在 handleError 中有这样一段相关代码:

throwException(
    root,
    erroredWork.return,
    erroredWork,
    thrownValue,
    workInProgressRootRenderLanes,
);
completeUnitOfWork(erroredWork);

核心代码需要继续深入到 throwException:

// 首先判断是否是为 promise
if (
    value !== null &&
    typeof value === 'object' &&
    typeof value.then === 'function'
  ) {
    const wakeable: Wakeable = (value: any);
    resetSuspendedComponent(sourceFiber, rootRenderLanes);
    // 获取到 Suspens 父组件
    const suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber);
    if (suspenseBoundary !== null) {
      suspenseBoundary.flags &= ~ForceClientRender;
      // 给 Suspens 父组件 打上一些标记,让 Suspens 父组件知道已经有异常抛出,需要渲染 fallback
      markSuspenseBoundaryShouldCapture(
        suspenseBoundary,
        returnFiber,
        sourceFiber,
        root,
        rootRenderLanes,
      );
      // We only attach ping listeners in concurrent mode. Legacy Suspense always
      // commits fallbacks synchronously, so there are no pings.
      if (suspenseBoundary.mode & ConcurrentMode) {
        attachPingListener(root, wakeable, rootRenderLanes);
      }
      // 将抛出的 promise 放入Suspens 父组件的 updateQueue 中,后续会遍历这个 queue 进行回调绑定
      attachRetryListener(suspenseBoundary, root, wakeable, rootRenderLanes);
      return;
    } 
  }

可以看到 throwException 逻辑主要是判断抛出的异常是不是 promise,如果是的话,就给 Suspens 父组件打上 ShoulCapture 的 flags,具体用处下面会讲到。并且把抛出的 promise 放入 Suspens 父组件的 updateQueue 中。

throwException 完成后会执行一次 completeUnitOfWork,根据 ShoulCapture 打上 DidCapture 的 flags。 并将下一个需要遍历的节点设置为 Suspense,也就是下一次遍历的对象依然是 Suspense。这也是之前提到的 Suspens 在整个 beginWork 阶段会遍历两次

源码解读 - 添加 promise 回调

在 Suspense 的 update queue 中,在 commit 阶段会遍历这个 updateQueue 添加回调函数,该功能在 commitMutationEffectsOnFiber 中。找到关于 Suspense 的部分,会有以下代码:

 if (flags & Update) {
        try {
          commitSuspenseCallback(finishedWork);
        } catch (error) {
          captureCommitPhaseError(finishedWork, finishedWork.return, error);
        }
        attachSuspenseRetryListeners(finishedWork);
      }
      return;

主要逻辑在 attachSuspenseRetryListeners 中:

function attachSuspenseRetryListeners(finishedWork: Fiber) {
  const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any);
  if (wakeables !== null) {
    finishedWork.updateQueue = null;
    let retryCache = finishedWork.statenode;
    if (retryCache === null) {
      retryCache = finishedWork.stateNode = new PossiblyWeakSet();
    }
    wakeables.forEach(wakeable => {
      // Memoize using the boundary fiber to prevent redundant listeners.
      const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
      // 判断一下这个 promise 是否已经绑定过一次了,如果绑定过则可以忽略
      if (!retryCache.has(wakeable)) {
        retryCache.add(wakeable);
        if (enableUpdaterTracking) {
          if (isDevToolsPresent) {
            if (inProgressLanes !== null && inProgressRoot !== null) {
              // If we have pending work still, associate the original updaters with it.
              restorePendingUpdaters(inProgressRoot, inProgressLanes);
            } else {
              throw Error(
                'Expected finished root and lanes to be set. This is a bug in React.',
              );
            }
          }
        }
        // 将 retry 绑定 promise 的 then 回调
        wakeable.then(retry, retry);
      }
    });
  }
}

attachSuspenseRetryListeners 整个逻辑就是绑定 promise 回调,并将绑定后的 promise 放入缓存,以免重复绑定。这里绑定的回调为 resolveRetryWakeable.bind(null, finishedWork, wakeable),在这个方法中又调用了 retryTimedOutBoundary 方法:

 if (retryLane === NoLane) {
    // TODO: Assign this to `suspenseState.retryLane`? to avoid
    // unnecessary entanglement?
    retryLane = requestRetryLane(boundaryFiber);
  }
  // TODO: Special case idle priority?
  const eventTime = requestEventTime();
  const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane);
  if (root !== null) {
    markRootUpdated(root, retryLane, eventTime);
    ensureRootIsScheduled(root, eventTime);
  }

看到 markUpdateLaneFromFiberToRoot 逻辑就比较清晰了,即在 Suspense 的组件上调度一次更新。也就是说,当动态组件的请求完成后,会执行 resolveRetryWakeable -> retryTimedOutBoundary,并且最终让 Suspense 进行一次更新。

源码解读-Suspense

之所以是将 Suspense 放在最后来分析,是因为对 Suspense 的处理涉及到多个状态,这些状态在之前的步骤中或许会被修改,因此在了解其他步骤之后再来看 Suspense 或许更容易理解。对于 Suspense 来说,在 workLoop 中可能会有 3 种不同的处理方式。每一次 beginWork Suspense 又会被访问两次,在源码中称为 first pass 和 second pass 。这两次会根据在 Suspense 的 flags 上是否存在 DidCapture 来进行不同操作。整个处理逻辑都在 updateSuspenseComponent 中。

首次渲染

beginWork - first pass,此时 DidCapture 不存在,Suspense 将 primary 组件作为子节点,访问子节点后会抛出异常。catch 时会设置 DidCapture 到 flags 上。对应的函数为 mountSuspensePrimaryChildren:

function mountSuspensePrimaryChildren(
  workInProgress,
  primaryChildren,
  renderLanes,
) {
  const mode = workInProgress.mode;
  const primaryChildProps: OffscreenProps = {
    mode: 'visible',
    children: primaryChildren,
  };
  const primaryChildFragment = mountWorkInProgressOffscreenFiber(
    primaryChildProps,
    mode,
    renderLanes,
  );
  primaryChildFragment.return = workInProgress;
  workInProgress.child = primaryChildFragment; // 子节点为 primaryChildFragment,下一次访问会抛出异常
  return primaryChildFragment;
}

beginWork - second pass,由于此时 DidCapture 存在,会将 primary 组件作为子节点,并将 fallback 组件作为 primary 组件的兄弟节点。但是直接返回 primary 组件,跳过 fallback 组件。对应的函数为 mountSuspenseFallbackChildren:

function mountSuspenseFallbackChildren(
  workInProgress,
  primaryChildren,
  fallbackChildren,
  renderLanes,
) {
  const mode = workInProgress.mode;
  const progressedPrimaryFragment: Fiber | null = workInProgress.child;
  const primaryChildProps: OffscreenProps = {
    mode: 'hidden',
    children: primaryChildren,
  };
  let primaryChildFragment;
  let fallbackChildFragment;
  primaryChildFragment.return = workInProgress;
  fallbackChildFragment.return = workInProgress;
  primaryChildFragment.sibling = fallbackChildFragment;
  workInProgress.child = primaryChildFragment; // 注意这里的子节点是 primaryChildFragment
  return fallbackChildFragment; // 但返回的却是 fallbackChildFragment,目的是为了跳过 primaryChild 的遍历
}

commit: 将挂载到 updateQueue 上的 promise 绑定回调,并清除 DidCapture。整个流程图如下:

primary 组件加载完成前的渲染

在首次渲染以及 primary 组件加载完成的期间,还可能会有其他组件更新而触发触发渲染,其逻辑为:

beginWork - first pass - DidCapture 不存在: 将 primary 组件作为子节点,如果 fallback 组件存在,则将其添加到 Suspense 组件的 deletions 中。访问子节点后会抛出异常。catch 时会设置 DidCapture 到 flags 上。 对应的函数为 updateSuspensePrimaryChildren:

function updateSuspensePrimaryChildren(
  current,
  workInProgress,
  primaryChildren,
  renderLanes,
) {
const currentPrimaryChildFragment: Fiber = (current.child: any);
  const currentFallbackChildFragment: Fiber | null =
    currentPrimaryChildFragment.sibling;
  const primaryChildFragment = updateWorkInProgressOffscreenFiber(
    currentPrimaryChildFragment,
    {
      mode: 'visible',
      children: primaryChildren,
    },
  );
  if ((workInProgress.mode & ConcurrentMode) === NoMode) {
    primaryChildFragment.lanes = renderLanes;
  }
  primaryChildFragment.return = workInProgress;
  primaryChildFragment.sibling = null;
  // 如果 currentFallbackChildFragment 存在,需要添加到 deletions 中
  if (currentFallbackChildFragment !== null) {
    const deletions = workInProgress.deletions;
    if (deletions === null) {
      workInProgress.deletions = [currentFallbackChildFragment];
      workInProgress.flags |= ChildDeletion;
    } else {
      deletions.push(currentFallbackChildFragment);
    }
  }
  workInProgress.child = primaryChildFragment;
  return primaryChildFragment;
}

beginWork - second pass - DidCapture 存在: 将 primary 组件作为子节点,将 fallback 组件作为 primary 组件的兄弟节点。并且清除deletions。因为此时 primary 组件还未加载完成,所以需要确保 fallback 组件不会被删除。对于的函数为:

function updateSuspenseFallbackChildren(
  current,
  workInProgress,
  primaryChildren,
  fallbackChildren,
  renderLanes,
) {
  const progressedPrimaryFragment: Fiber = (workInProgress.child: any);
    primaryChildFragment = progressedPrimaryFragment;
    primaryChildFragment.childLanes = NoLanes;
    primaryChildFragment.pendingProps = primaryChildProps;
    if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
      primaryChildFragment.actualDuration = 0;
      primaryChildFragment.actualStartTime = -1;
      primaryChildFragment.selfBaseDuration =
        currentPrimaryChildFragment.selfBaseDuration;
      primaryChildFragment.treeBaseDuration =
        currentPrimaryChildFragment.treeBaseDuration;
    }
    // 清除 deletions,确保 fallback 可以展示
    workInProgress.deletions = null;
    let fallbackChildFragment;
    if (currentFallbackChildFragment !== null) {
    fallbackChildFragment = createWorkInProgress(
      currentFallbackChildFragment,
      fallbackChildren,
    );
  } else {
    fallbackChildFragment = createFiberFromFragment(
      fallbackChildren,
      mode,
      renderLanes,
      null,
    );
    fallbackChildFragment.flags |= Placement;
  }
  fallbackChildFragment.return = workInProgress;
  primaryChildFragment.return = workInProgress;
  primaryChildFragment.sibling = fallbackChildFragment;
  workInProgress.child = primaryChildFragment; // 同样的操作,workInProgress.child 为 primaryChildFragment
  return fallbackChildFragment; // 但是返回 fallbackChildFragment
  }

commit: 清除 DidCapture。 整个流程图如下:

primary 组件加载完成时的渲染

加载完成之后会触发 Suspense 的更新,此时为:

beginWork - first pass - DidCapture 不存在: 将 primary 组件作为子节点,如果 fallback 组件存在,则将其添加到 Suspense 组件的 deletions 中。由于此时 primary 组件加载完成,访问子节点不会抛出异常。处理的函数同样为 updateSuspensePrimaryChildren,这里就不再贴出来。

可以看出,primary 组件加载完成后就不会抛出异常,因此不会进入到 second pass,那么就不会有清除 deletions 的操作,因此本次完成后 fallback 仍然在删除列表中,最终会被删除。达到了切换到 primary 组件的目的。整体流程为:

利用 Suspense 自己实现数据加载

在我们明白了 lazy + Suspense 的原理之后,可以自己利用 Suspense 来进行数据加载,其无非就是三种状态:

  • 初始化:查询数据,抛出 promise
  • 加载中: 直接抛出 promise
  • 加载完成:设置 promise 返回的数据

按照这样的思路,设计一个简单的数据加载功能:

// 模拟请求 promise
function mockapi(){
  return delay(5000).then(() => "data fetched")
}
// 处理请求状态变更
function fetchData(){
  let status = "uninit"
  let data = null
  let promise = null
  return () => {
    switch(status){
      // 初始状态,发出请求并抛出 promise
      case "uninit": {
        const p = mockApi()
          .then(x => {
            status = "resolved"
            data = x
          })
          status = "loading"
          promise = p
        throw promise
      };
      // 加载状态,直接抛出 promise
      case "loading": throw promise;
      // 如果加载完成直接返回数据
      case "resolved": return data;
      default: break;
    }
  }
}
const reader = fetchData()
function TestDataLoad(){
  const data = reader()
  return (
    <p>{data}</p>
  )
}
function App() {
  const [count, setCount] = useState(1)
  useEffect(() => {
    setInterval(() => setCount(c => c > 100 ? c: c + 1), 1000)
  }, [])
  return (
     <>
        <Suspense fallback={"loading"}>
          <TestDataLoad/>
        </Suspense>
        <p>count: {count}</p>
     </>
  )
}

结果为一开始显示 fallback 中的 loading,数据加载完成后显示 data fetched。你可以在这里进行在线体验:codesandbox.io/s/suspiciou…

关于更多使用 Suspense 进行数据加载这方面的内容,可以参考 react 的官方文档: 17.reactjs.org/docs/concur… 。

以上就是react Suspense工作原理解析的详细内容,更多关于react Suspense工作原理的资料请关注编程网其它相关文章!

--结束END--

本文标题: reactSuspense工作原理解析

本文链接: https://lsjlt.com/news/168107.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • reactSuspense工作原理解析
    目录Suspense 基本应用Suspense 原理基本流程源码解读 - primary 组件源码解读 - 异常捕获源码解读 - 添加 promise 回调源码解读-Suspense...
    99+
    2024-04-02
  • BeegoAutoRouter工作原理解析
    目录一、前言 二、从一个例子入手✨AutoRouter的解析规则:三、AutoRouter是如何工作的结语一、前言 Beego Web框架应该是国内Go语言社区第一个框架,个人觉得...
    99+
    2024-04-02
  • Android Handler工作原理解析
    简介 在Android 中,只有主线程才能操作 UI,但是主线程不能进行耗时操作,否则会阻塞线程,产生 ANR 异常,所以常常把耗时操作放到其它子线程进行。如果在子线程中需要...
    99+
    2022-06-06
    handler Android
  • Python 装饰器工作原理解析
    #!/usr/bin/env python #coding:utf-8 """ 装饰器实例拆解 """ def login00(func):     print('00请通过验证用户!')     return func def ...
    99+
    2023-01-31
    工作原理 Python
  • Pinia介绍及工作原理解析
    目录什么是Pinia如何使用Pinia安装创建store在组件中使用store在模板中使用storePinia是如何工作的什么是Pinia Pinia是Vue 3的状态管理库,它提...
    99+
    2023-03-06
    Pinia工作原理 Pinia原理
  • Vue状态管理工具Vuex工作原理解析
    目录一、什么是vuex二、vuex的工作方式三、vuex的使用场景四、工作流程五、vuex的核心API六、应用七、vuex的工作流程一、什么是vuex Vuex是vue项目的状态管理...
    99+
    2023-02-01
    Vue状态管理工具Vuex Vue状态管理器Vuex Vue Vuex
  • MySQL SSL 连接的工作原理解析
    MySQL是一款使用广泛的关系型数据库管理系统,它的安全性一直备受关注。SSL(Secure Socket Layer)是一种加密通信协议,用于提供安全的数据传输。MySQL SSL连接的工作原理即在MySQL数据库中使用SSL协议建立安全...
    99+
    2023-10-22
    用于存储和管理数据。 SSL: SSL(Secure Sockets Layer) 安全套接层
  • DHCP的工作原理分析
    这篇文章主要介绍了DHCP的工作原理分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。DHCP动态主机配置协议:计算机用来获得配置信息的协议。DHCP容许给某一计算机赋以IP...
    99+
    2023-06-27
  • 深入解析Golang中锁的工作原理
    Golang中锁的工作原理深度剖析引言:在并发编程中,避免竞态条件(race condition)是至关重要的。为了实现线程安全,Golang提供了丰富的锁机制。本文将深入剖析Golang中锁的工作原理,并提供具体的代码示例。一、互斥锁(M...
    99+
    2023-12-28
    (lock) 工作原理 (Working Principle) Golang (Golang)
  • PHP 异步通知的工作原理解析
    PHP 异步通知的工作原理解析 在Web开发过程中,异步通知是一种非常重要的机制,能够实现服务器端和客户端之间的非阻塞通信,提升用户体验和系统性能。在PHP开发过程中,实现异步通知通常...
    99+
    2024-03-10
    php异步 通知原理 工作解析
  • SELINUX工作原理详解
    1. 简介 SElinux带给Linux的主要价值是:提供了一个灵活的,可配置的MAC机制。     Security-Enhanced Linux (SELinux)由以下两部分组成: ...
    99+
    2022-06-04
    SELINUX 工作原理
  • 详解MyBatis工作原理
    目录一、Mybatis工作原理二、Mybatis运行原理总结一、Mybatis工作原理 Mybatis分层框架图 Mybatis工作原理图 源码分析:一般都是从helloworl...
    99+
    2024-04-02
  • Springboot工作原理详解
    目录 一、Spring Boot 1.1.Spring Boot 优点  二、SpringBoot 运行原理 1.1. pom.xml  2.2. 主启动类的配置 3.3. 主启动类的运行 三、自动配置原理 3.1、启动类上注解的作用  3...
    99+
    2023-10-10
    spring boot java spring
  • 【mybatis】mybatis 拦截器工作原理源码解析
    mybatis 拦截器工作原理(JDK动态代理) 1. mybatis 拦截器案例 场景:分页查询,类似成熟产品:pagehelper, 这里只做简单原理演示 1.0 mybatis全局配置 SqlMapConfig.xml ...
    99+
    2015-01-18
    【mybatis】mybatis 拦截器工作原理源码解析
  • Linux NFS机制工作原理及实例解析
    什么是NFS? network file system 网络文件系统 通过网络存储和组织文件的一种方法或机制。 为什么要用NFS? 前端所有的应用服务器接收到用户上传的图片、文件、视频,都会统一放到后...
    99+
    2022-06-03
    Linux NFS机制
  • 怎样理解HTTPS工作原理
    怎样理解HTTPS工作原理,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。当你打开浏览器,访问某个网站,如果网址旁有个小锁,代表访问的网址是安全的,反之不安全。当我们没有看到...
    99+
    2023-06-17
  • Node.js中require的工作原理浅析
    几乎所有的Node.js开发人员可以告诉你`require()`函数做什么,但我们又有多少人真正知道它是如何工作的?我们每天都使用它来加载库和模块,但它的行为,对于我们来说反而是一个谜。 出于好奇,我钻研了...
    99+
    2022-06-04
    工作原理 Node js
  • Memcached工作原理的示例分析
    这篇文章主要介绍Memcached工作原理的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Memcached的工作原理。Memcached处理的原子是每一个(key,valu...
    99+
    2024-04-02
  • MyBatis工作原理的示例分析
    这篇文章给大家分享的是有关MyBatis工作原理的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、Mybatis工作原理Mybatis分层框架图Mybatis工作原理图源码分析:一般都是从hellowo...
    99+
    2023-06-15
  • Struts2工作原理的示例分析
    这篇文章主要介绍Struts2工作原理的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、工作原理在Struts2框架中的处理大概分为以下几个步骤      ...
    99+
    2023-05-30
    struts2
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作