React16源码: React中的renderRoot的源码实现

renderRoot


1 )概述

  • renderRoot 是一个非常复杂的方法
  • 这个方法里处理很多各种各样的逻辑, 它主要的工作内容是什么?
  • A. 它调用 workLoop 进行循环单元更新
    • 遍历整个 Fiber Tree,把每一个组件或者 dom 节点对应的
    • Fiber 节点拿出来单一的进行更新,这是一个循环的操作
    • 把整棵 Fiber Tree 都遍历一遍,这就是 workLoop
  • B. 捕获错误并进行处理
    • 在进行每一个单元更新的时候,这个遍历逻辑的时候,有可能会出现一些错误
    • 有些是可预期的,比如说是 Suspense 的功能, throw 一个 Promise 对象
    • 这个时候,是我们要特殊的对它进行处理的
    • 有一些是不可预期的,比如说, render 里面报了一个错误
    • 我们也要对它进行一些处理,要告诉我们的react这个地方
    • 我们出现了一个错误,在react16之后,有了 Error Boundary
    • 可以在组件内捕获渲染的错误
  • C. 在走完流程之后要进行善后
    • 因为流程走完之后会有各种不同的情况
    • 比如说有错误的情况,比如说有任务被挂起的情况,也就是Suspense的情况
    • 这些任务,都要按照特定的逻辑给它进行一些处理
  • 这就是 renderRoot 这个方法,它的主要的核心工作内容

2 )流程图

  • 进入 renderRoot, 它里面有一个很核心的循环 do while workLoop
  • 这个while循环就是调用 workLoop 对整棵树,它每个节点进行一个遍历,并且拿出来单独进行更新
  • 因为每个 Fiber节点上,如果有更新的话,它会记入 updateQueen
  • 我们通过 updateQueen 上是否有内容来判断它是否要进行更新
  • 以及可以计算出它的新的 state,得到最新的 children,拿到所有最新的节点
  • 在 workLoop 的过程当中,它在做什么呢?
  • nextUnitOfWork 就是每一个节点在遍历的过程当中,它自己更新完之后,它会返回它的第一个child
  • 它的第一个child 就是作为 nextUnitOfWork,因为我们执行了一个节点的更新之后,我们需要返回
  • 返回之后,我们要判断一些逻辑
  • 比如,对于异步的操作,每个节点更新完之后都要判断 !shouldYield()
  • 判断我们现在的时间片是否还有?如果还有的话,再继续,如果没有的话,就要跳出了
  • 接下去就会执行 performUnitOfWork
  • 之后,执行 beginWork, completeUnitOfWork 这些,当然中间会判断是否有 next
  • next就是我在更新完一个结点之后,它是否还有下一个节点需要更新
  • 如果有next的情况,我们就返回,然后去判断这个逻辑是否还有
  • 这就是整个对整个 fiber tree 每个节点的遍历更新
  • 在这个更新过程当中,如果有任何catch,就是捕获到异常
  • 那么首先会进行一系列的判断,然后对它执行 throwException 或者是 onUncaughtError
  • 它们对应的逻辑会不一样, 如果这个节点它是可处理的错误
  • 我会直接对它进行 completeUnitOfWork() 因为更新到这个节点之后,它抛出错误了
  • 说明我们这个节点下面的所有子节点都不需要再更新了
  • 执行完成之后,我们就会调用 continue,对于这个 do while 循环,它又继续调用 workLoop
  • 也就是说我们把一棵子数的错误处理完之后,它还可以继续对别的子树进行更新
  • 整体更新完之后,就会 break,之后会有各种不同的情况,比如说有致命的错误等
  • 它们都会调用不同的逻辑进行一个处理,这里 nextRenderDidError 是一个可处理的错误
  • 比如说, 是可以被捕获的错误,有组件能捕获它,而 nextLatestAbsoluteTimeoutMs 是抛出promise的错误
  • 它是一个被挂起的一个任务,对应要执行一个被被挂起的操作, 最后如果上面的情况都没有出现
  • 直接 onComplete,之后就可以在root节点上 set finishedwork,这样, 就可以对它整体进行一个更新
  • 就可以执行 completeRoot,就可以把 fiber树 变成我们真正的dom 树去更新整个页面
  • 这就是整个 renderRoot 它的一个逻辑,简单总结
    • 先正常的执行每个单元的更新
    • 然后捕获到任何错误进行一定的处理
    • 最终把整个树遍历完之后根据不同的情况再进行一个处理

3 )源码

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

先看 renderRoot 的代码

function renderRoot(root: FiberRoot,isYieldy: boolean,isExpired: boolean,
): void {invariant(!isWorking,'renderRoot was called recursively. This error is likely caused ' +'by a bug in React. Please file an issue.',);isWorking = true;ReactCurrentOwner.currentDispatcher = Dispatcher;const expirationTime = root.nextExpirationTimeToWorkOn;// Check if we're starting from a fresh stack, or if we're resuming from// previously yielded work.// 刚进来的时候,进行这个判断// nextRoot 和 nextRenderExpirationTime 对应着接下来要渲染的节点和对应的过期时间 这是两个公共变量// 在这种情况下,说明调用这个方法的时候,接收到的参数和之前的不一样,可能就是之前的异步任务被新进来的高优先级的任务给打断了if (expirationTime !== nextRenderExpirationTime ||root !== nextRoot ||nextUnitOfWork === null) {// Reset the stack and start working from the root.resetStack();nextRoot = root;nextRenderExpirationTime = expirationTime;// nextUnitOfWork 来自于 createWorkInProgress// 就是把当前的应用的状态对应的Fiber节点,拷贝了一份叫做 workInProgress 的对象// 因为我们不能直接在当前对象的Fiber节点上操作,它会影响我们目前的dom节点展示的样子// 所以要复制一份拷贝,对拷贝进行操作,workInProgress 和 current 之间会有一个转换的关系// 在renderRoot开始之后,我们真正操作的节点都是 workInProgress,没有直接在 current 上操作nextUnitOfWork = createWorkInProgress(nextRoot.current,null,nextRenderExpirationTime,);// 这种和不同expirationTime会有关系root.pendingCommitExpirationTime = NoWork;if (enableSchedulerTracing) {// Determine which interactions this batch of work currently includes,// So that we can accurately attribute time spent working on it,// And so that cascading work triggered during the render phase will be associated with it.const interactions: Set<Interaction> = new Set();root.pendingInteractionMap.forEach((scheduledInteractions, scheduledExpirationTime) => {if (scheduledExpirationTime <= expirationTime) {scheduledInteractions.forEach(interaction =>interactions.add(interaction),);}},);// Store the current set of interactions on the FiberRoot for a few reasons:// We can re-use it in hot functions like renderRoot() without having to recalculate it.// We will also use it in commitWork() to pass to any Profiler onRender() hooks.// This also provides DevTools with a way to access it when the onCommitRoot() hook is called.root.memoizedInteractions = interactions;if (interactions.size > 0) {const subscriber = __subscriberRef.current;if (subscriber !== null) {const threadID = computeThreadID(expirationTime,root.interactionThreadID,);try {subscriber.onWorkStarted(interactions, threadID);} catch (error) {// Work thrown by an interaction tracing subscriber should be rethrown,// But only once it's safe (to avoid leaveing the scheduler in an invalid state).// Store the error for now and we'll re-throw in finishRendering().if (!hasUnhandledError) {hasUnhandledError = true;unhandledError = error;}}}}}}let prevInteractions: Set<Interaction> = (null: any);if (enableSchedulerTracing) {// We're about to start new traced work.// Restore pending interactions so cascading work triggered during the render phase will be accounted for.prevInteractions = __interactionsRef.current;__interactionsRef.current = root.memoizedInteractions;}let didFatal = false;startWorkLoopTimer(nextUnitOfWork);// 上面初始化工作做完之后,就开始 workLoop// 如果有 catch 就会有一大段处理逻辑, 这里先跳过// do {try {workLoop(isYieldy);} catch (thrownValue) {if (nextUnitOfWork === null) {// This is a fatal error.didFatal = true;onUncaughtError(thrownValue);} else {if (__DEV__) {// Reset global debug state// We assume this is defined in DEV(resetCurrentlyProcessingQueue: any)();}const failedUnitOfWork: Fiber = nextUnitOfWork;if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);}// TODO: we already know this isn't true in some cases.// At least this shows a nicer error message until we figure out the cause.// https://github.com/facebook/react/issues/12449#issuecomment-386727431invariant(nextUnitOfWork !== null,'Failed to replay rendering after an error. This ' +'is likely caused by a bug in React. Please file an issue ' +'with a reproducing case to help us find it.',);const sourceFiber: Fiber = nextUnitOfWork;let returnFiber = sourceFiber.return;if (returnFiber === null) {// This is the root. The root could capture its own errors. However,// we don't know if it errors before or after we pushed the host// context. This information is needed to avoid a stack mismatch.// Because we're not sure, treat this as a fatal error. We could track// which phase it fails in, but doesn't seem worth it. At least// for now.didFatal = true;onUncaughtError(thrownValue);} else {throwException(root,returnFiber,sourceFiber,thrownValue,nextRenderExpirationTime,);nextUnitOfWork = completeUnitOfWork(sourceFiber);continue;}}}break;} while (true);if (enableSchedulerTracing) {// Traced work is done for now; restore the previous interactions.__interactionsRef.current = prevInteractions;}// We're done performing work. Time to clean up.isWorking = false;ReactCurrentOwner.currentDispatcher = null;resetContextDependences();// 在处理完 workLoop 后这里会有各种不同的判断// 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一定会匹配// 因为已经跳出 workLoop 了,说明一定有 react没有意识到的错误,所以调用 onYieldif (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 errorif (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 &&!isExpired) {root.didError = true;const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime);const rootExpirationTime = (root.expirationTime = Sync);onSuspend(root,rootWorkInProgress,suspendedExpirationTime,rootExpirationTime,-1, // Indicates no timeout);return;}}// 注意这里的错误if (!isExpired && 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 Differenceconst rootExpirationTime = root.expirationTime;onSuspend(root,rootWorkInProgress,suspendedExpirationTime,rootExpirationTime,msUntilTimeout,);return;}// Ready to commit.onComplete(root, rootWorkInProgress, expirationTime);
}
  • renderRoot 代码会相对比较长,要把代码的区块进行一个区分
  • 一些原版英文注释,和我添加的中文注释如上

现在来看下 workLoop 的源码

function workLoop(isYieldy) {if (!isYieldy) {// Flush work without yieldingwhile (nextUnitOfWork !== null) {nextUnitOfWork = performUnitOfWork(nextUnitOfWork);}} else {// Flush asynchronous work until the deadline runs out of time.while (nextUnitOfWork !== null && !shouldYield()) {nextUnitOfWork = performUnitOfWork(nextUnitOfWork);}}
}
  • 它接收一个 isYieldy 作为参数
  • 这个参数意味着是否可以被中断
  • Sync的任务和已超时的异步任务都是不可中断的
  • 如果是不可中断的,只要有 nextUnitOfWork
  • 就会继续调用 performUnitOfWork
  • 如果是可以中断的,就通过判断 !shouldYield()
  • 来看当前时间片中是否还有足够的时间继续渲染下一个节点

再来看下 performUnitOfWork

function performUnitOfWork(workInProgress: Fiber): Fiber | null {// The current, flushed, state of this fiber is the alternate.// Ideally nothing should rely on this, but relying on it here// means that we don't need an additional field on the work in// progress.const current = workInProgress.alternate;// See if beginning this work spawns more work.startWorkTimer(workInProgress);if (__DEV__) {ReactCurrentFiber.setCurrentFiber(workInProgress);}if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {stashedWorkInProgressProperties = assignFiberPropertiesInDEV(stashedWorkInProgressProperties,workInProgress,);}let next;if (enableProfilerTimer) {if (workInProgress.mode & ProfileMode) {startProfilerTimer(workInProgress);}next = beginWork(current, workInProgress, nextRenderExpirationTime);workInProgress.memoizedProps = workInProgress.pendingProps;if (workInProgress.mode & ProfileMode) {// Record the render duration assuming we didn't bailout (or error).stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);}} else {next = beginWork(current, workInProgress, nextRenderExpirationTime);workInProgress.memoizedProps = workInProgress.pendingProps;}if (__DEV__) {ReactCurrentFiber.resetCurrentFiber();if (isReplayingFailedUnitOfWork) {// Currently replaying a failed unit of work. This should be unreachable,// because the render phase is meant to be idempotent, and it should// have thrown again. Since it didn't, rethrow the original error, so// React's internal stack is not misaligned.rethrowOriginalError();}}if (__DEV__ && ReactFiberInstrumentation.debugTool) {ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);}if (next === null) {// If this doesn't spawn new work, complete the current work.next = completeUnitOfWork(workInProgress);}ReactCurrentOwner.current = null;return next;
}
  • 它声明了一个 next 变量,next = beginWork(…), 这里涉及到对每个节点的更新
    • 更新完一个节点之后,它会返回它的下一个节点
    • 会更新 workInProgress.memoizedProps, 节点已经更新完了
    • 最新的 props 已经变成目前正在用的 props
    • 先跳过
  • 跳过 DEV 的代码
  • 如果 next === null 说明这个节点已经更新到子树的叶子节点了
    • 这棵子树就可以结束了
    • 结束就调用 completeUnitOfWork
    • 它也会返回它的下一个节点
  • 最后,return next
    • 在 workLoop 函数中可看到,它会赋值给 nextUnitOfWork
      // 参考其中一个 while
      while (nextUnitOfWork !== null) {nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
      }
      
    • 所以,真正到 nextUnitOfWork 为 null 的情况是它到了根节点,即 FiberRoot 节点
    • 它的 return 是 null,这时就跳出了 while 循环了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/628923.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

万户 ezOFFICE ezflow_gd.jsp SQL注入漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品,统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日…

DC电源模块与AC电源模块的对比分析

DC电源模块与AC电源模块的对比分析 BOSHIDA DC电源模块和AC电源模块是两种常见的电源模块&#xff0c;它们在供电方式、稳定性、适用范围等方面有所不同&#xff0c;下面是它们的对比分析&#xff1a; 1. 供电方式&#xff1a; DC电源模块通过直流电源供电&#xff0c;通常使用…

【Linux】Linux 系统编程——which 命令

文章目录 1.命令概述2.命令格式3.常用选项4.相关描述5.参考示例 1.命令概述 which 命令用于定位执行文件的路径。当输入一个命令时&#xff0c;which 会在环境变量 PATH 所指定的路径中搜索每个目录&#xff0c;以查找指定的可执行文件。 2.命令格式 which [选项] 命令名3.常…

生产力与生产关系 —— 浅析爱泼斯坦事件 之 弱电控制强电原理

据网络文字与视频资料&#xff0c;爱泼斯坦事件是犹太精英阶层&#xff0c;为了掌控美国国家机器为犹太利益集团服务&#xff0c;而精心设下的一个局。本文先假设这个结论成立&#xff0c;并基于此展开讨论。 我们知道&#xff0c;弱电管理强电是电气工程中的一门专门学问&…

Mysql 数据库DDL 数据定义语言——数据库,数据表的创建

DDL&#xff1a;数据定义语言&#xff0c;用来定义数据库对象&#xff08;数据库&#xff0c;表&#xff0c;字段&#xff09;—Database Definition Language 1、登录数据库&#xff0c;输入用户名和密码 mysql -ufdd -p990107Wjl2、查看数据库 show databases;3、创建一个…

MySQL面试题 | 12.精选MySQL面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

什么情况下物理服务器会运行出错?

​  物理服务器&#xff0c;也称为裸机服务器&#xff0c;一般可以提供高性能计算水平和巨大的存储容量。然而&#xff0c;它们也难免会遇到一些问题。运行出错时&#xff0c;可能会导致停机和数据丢失。在这篇文章中&#xff0c;介绍了常见的物理服务器在一些情况下显示出错…

【白话机器学习的数学】读书笔记(2)学习回归

二、学习回归 1. y y y与 f θ ( x ) f_\theta(x) fθ​(x) y y y 是实际数据x对应的值 f θ ( x ) f_\theta(x) fθ​(x)是我们构造出来的函数&#xff0c;例如 f θ ( x ) θ 0 θ 1 x f_\theta(x) \theta_0 \theta_1 x fθ​(x)θ0​θ1​x 所以我们希望这两个越接近&…

【视觉SLAM十四讲学习笔记】第五讲——相机模型

专栏系列文章如下&#xff1a; 【视觉SLAM十四讲学习笔记】第一讲——SLAM介绍 【视觉SLAM十四讲学习笔记】第二讲——初识SLAM 【视觉SLAM十四讲学习笔记】第三讲——旋转矩阵 【视觉SLAM十四讲学习笔记】第三讲——旋转向量和欧拉角 【视觉SLAM十四讲学习笔记】第三讲——四元…

苹果MAC怎么清理内存?苹果MAC清理内存的方法

很多使用苹果电脑的用户都喜欢在同时运行多个软件&#xff0c;不过这样会导致在运行一些大型软件的时候出现不必要的卡顿现象&#xff0c;这时候我们就可以去清理下内存&#xff0c;不过很多人可能并不知道正确的清内存方式&#xff0c;下面就和小编一起来看看吧。 苹果MAC清理…

Shell脚本同时调用#!/bin/bash和#!/usr/bin/expect

如果你想在一个脚本中同时使用bash和expect&#xff0c;你可以将expect部分嵌入到bash脚本中。以下是一个示例&#xff1a; #!/bin/bash# 设置MySQL服务器地址、端口、用户名和密码 MYSQL_HOST"localhost" MYSQL_PORT"3306" MYSQL_USER"your_usernam…

Maven 依赖传递和冲突、继承和聚合

一、依赖传递和冲突 1.1 Maven 依赖传递特性 1.1.1 概念 假如有三个 Maven 项目 A、B 和 C&#xff0c;其中项目 A 依赖 B&#xff0c;项目 B 依赖 C。那么我们可以说 A 依赖 C。也就是说&#xff0c;依赖的关系为&#xff1a;A—>B—>C&#xff0c; 那么我们执行项目 …

PDF有编辑密码怎么办

目录 注意&#xff1a; windows方法&#xff1a; 1 python 下载 2 打开命令行 3 安装 pikepdf 4 编写python脚本 5 使用py脚本 6解密完成 Linux方法&#xff1a; 注意&#xff1a; 此方法可以用于破解PDF的编辑密码&#xff0c;而不是PDF的打开密码 当遇到类似如下问…

热压机PLC数据采集远程监控物联网解决方案

热压机PLC数据采集远程监控物联网解决方案 随着工业4.0时代的到来&#xff0c;智能制造已经成为制造业发展的重要方向。在热压机领域&#xff0c;PLC数据采集远程监控物联网解决方案为提高生产效率、降低维护成本、优化生产工艺提供了有效的手段。 一、热压机PLC数据采集远程…

canvas绘制美队盾牌

查看专栏目录 canvas示例教程100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

【Rust学习】安装Rust环境

本笔记为了记录学习Rust过程&#xff0c;内容如有错误请大佬指教 使用IDE&#xff1a;vs code 参考教程&#xff1a;菜鸟教程链接: 菜鸟教程链接: Rust学习 Rust入门安装Rust编译环境Rust 编译工具 构建Rust 工程目录 Rust入门 安装Rust编译环境 因为我已经安装过VSCode了&am…

解决若依Vue3前后端分离---路由切换时显示白屏

解决若依Vue3前后端分离---路由切换时显示白屏 1.问题重述 解决基于Vue3若依前后端分离项目中出现的路由正常切换但是就是不显示数据的问题&#xff0c;也就是不发起网络请求的问题。 找到如下位置中AppMain.vue文件 将除了css中的代码进行替换成如下的代码。 <template&g…

kylin集群负载均衡(kylin3,hbaseRIF问题)

hbase历险记 目录 hbase历险记 寻找问题 分析原因 解决方案 方案1&#xff08;资源问题、失败&#xff09; 方案2&#xff08;成功&#xff09; 寻找问题 不知道你是不是有这样的疑惑。我kylin是个单机&#xff0c;我使用的hbase是个集群&#xff0c;但内存全在某一台机…

vue2使用qiankun微前端(跟着步骤走可实现)

需求&#xff1a;做一个vue2的微前端&#xff0c;以vue2为主应用&#xff0c;其他技术栈为子应用&#xff0c;比如vue3&#xff0c;本文章只是做vue2一套的微前端应用实现&#xff0c;之后解决的一些问题。vue3子应用可以看我另一篇vue3vitets实现qiankun微前端子应用-CSDN博客…

前端八股文(性能优化篇)

目录 1.CDN的概念 2.CDN的作用 3.CDN的原理 4.CDN的使用场景 5.懒加载的概念 6.懒加载的特点 7.懒加载的实现原理 8.懒加载与预加载的区别 9.回流与重绘的概念及触发条件 &#xff08;1&#xff09;回流 &#xff08;2&#xff09;重绘 10. 如何避免回流与重绘&#…