启动 React APP 后经历了哪些过程
本文作者为 360 奇舞团前端开发工程师
前言
本文中使用的
React版本为18,在摘取代码的过程中删减了部分代码,具体以源代码为准。
在React 18里,通过ReactDOM.createRoot创建根节点。并且通过调用原型链上的render来渲染。 本文主要是从以下两个方法来介绍展开。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';ReactDOM.createRoot(document.getElementById('root')).render(<React.StrictMode><App /></React.StrictMode>
); 一、createRoot()
createRoot这个方法主要是用来创建FiberRoot(全局唯一,保存全局状态)和RootFiber(是应用里的第一个fiber对象),并将其关系关联起来。主要有以下过程:
校验
container有效性,以及处理options参数创建
FiberRoot和rootFiber,并关联起来事件代理
返回
ReactDOMRoot实例
function createRoot(container: Element | Document | DocumentFragment,options?: CreateRootOptions,
): RootType {// 校验合法性,以及处理options参数,此处省略if (!isValidContainer(container)) {//...}// 调用 createFiberRoot,创建FiberRoot和rootFiber,并关联关系,最终返回FiberRoot。FiberRoot.current = rootFiber; rootFiber.stateNode = FiberRoot;const root = createContainer(container,ConcurrentRoot,null,isStrictMode,concurrentUpdatesByDefaultOverride,identifierPrefix,onRecoverableError,transitionCallbacks,);// 标记container和rootFiber关系 container['__reactContainer$' + randomKey] = root.currentmarkContainerAsRoot(root.current, container); const rootContainerElement: Document | Element | DocumentFragment =container.nodeType === COMMENT_NODE? (container.parentNode: any): container;listenToAllSupportedEvents(rootContainerElement); // 事件代理// 实例化,挂载render,unmount方法return new ReactDOMRoot(root); // this._internalRoot = root;
} 关系结构示意图
二、render()
render主要是通过调用updateContainer,将组件渲染在页面上。
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function(children: ReactNodeList,
): void {const root = this._internalRoot;if (root === null) {throw new Error('Cannot update an unmounted root.');}updateContainer(children, root, null, null);
}; updateContainer
updateContainer主要执行了以下步骤:
获取当前时间
eventTime和任务优先级lane,调用createUpdate生成update;执行
enqueueUpdate将更新添加到更新队列里,并返回FiberRoot;scheduleUpdateOnFiber调度更新;
function updateContainer(element: ReactNodeList,container: OpaqueRoot,parentComponent: ?React$Component<any, any>,callback: ?Function,
): Lane {const current = container.current; // rootFiberconst eventTime = requestEventTime(); // 更新触发时间const lane = requestUpdateLane(current); // 获取任务优先级// ... context 处理 // 创建update:{eventTime, lane, tag: UpdateState // 更新类型, payload: null, callback: null, next: null, // 下一个更新}const update = createUpdate(eventTime, lane); update.payload = {element}; // element首次渲染时为Appcallback = callback === undefined ? null : callback;if (callback !== null) {update.callback = callback;}const root = enqueueUpdate(current, update, lane); // 将update添加到concurrentQueues队列里,返回 FiberRootif (root !== null) {scheduleUpdateOnFiber(root, current, lane, eventTime); // 调度entangleTransitions(root, current, lane);}return lane;
} 调度阶段
调度入口:scheduleUpdateOnFiber
主要有以下过程:
在
root上标记更新通过执行
ensureRootIsScheduled来调度任务
function scheduleUpdateOnFiber(root: FiberRoot,fiber: Fiber,lane: Lane,eventTime: number,
) {markRootUpdated(root, lane, eventTime); // 在root上标记更新 // root.pendingLanes |= lane; 将update的lane放到root.pendingLanes// 设置lane对应事件时间 root.eventTimes[laneToIndex(lane)] = eventTime;if ((executionContext & RenderContext) !== NoLanes &&root === workInProgressRoot) { // 更新是在渲染阶段调度提示错误 ...} else { // 正常更新// ...ensureRootIsScheduled(root, eventTime); // 调度任务// ...}
} 调度优先级:ensureRootIsScheduled
该函数用于调度任务,一个root只能有一个任务在执行
设置任务的过期时间,有过期任务加入到
expiredLanes中获取下一个要处理的优先级,没有要执行的则退出
判断优先级相等则复用,否则取消当前执行的任务,重新调度。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {const existingCallbackNode = root.callbackNode; // 正在执行的任务// 遍历root.pendingLanes,没有过期时间设置root.expirationTimes,有过期时间判断是否过期,是则加入到root.expiredLanes中markStarvedLanesAsExpired(root, currentTime);// 过期时间设置 root.expirationTimes = currentTime+t(普通任务5000ms,用户输入250ms);// 获取要处理的下一个lanesconst nextLanes = getNextLanes(root,root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,);// 没有要执行的lanesif (nextLanes === NoLanes) {if (existingCallbackNode !== null) {// 取消正在执行的任务cancelCallback(existingCallbackNode);}root.callbackNode = null;root.callbackPriority = NoLane;return;}const newCallbackPriority = getHighestPriorityLane(nextLanes); // 获取最高优先级的laneconst existingCallbackPriority = root.callbackPriority;// 优先级相等复用已有的任务if (existingCallbackPriority === newCallbackPriority &&!(__DEV__ &&ReactCurrentActQueue.current !== null &&existingCallbackNode !== fakeActCallbackNode)) {return;}// 优先级变化,取消正在执行的任务,重新调度if (existingCallbackNode != null) {cancelCallback(existingCallbackNode);}let newCallbackNode; // 注册调度任务// 同步任务,不可中断// 1. 调用scheduleSyncCallback将任务添加到队列syncQueue里;// 2. 创建微任务,调用flushSyncCallbacks,遍历syncQueue队列执行performSyncWorkOnRoot,清空队列;if (newCallbackPriority === SyncLane) {if (root.tag === LegacyRoot) {scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));} else {scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));}if (supportsMicrotasks) {// 支持微任务scheduleMicrotask(() => {if ((executionContext & (RenderContext | CommitContext)) ===NoContext) {flushSyncCallbacks();}});} else {scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);}newCallbackNode = null;} else {let schedulerPriorityLevel;switch (lanesToEventPriority(nextLanes)) {// ...case DefaultEventPriority:schedulerPriorityLevel = NormalSchedulerPriority;break;default:schedulerPriorityLevel = NormalSchedulerPriority;break;}// 非同步任务,可中断// 1. 维护了两个队列 timerQueue taskQueue// 2. 通过requestHostCallback开启宏任务执行任务newCallbackNode = scheduleCallback(schedulerPriorityLevel,performConcurrentWorkOnRoot.bind(null, root),);}root.callbackPriority = newCallbackPriority;root.callbackNode = newCallbackNode;
} 调度任务 scheduleSyncCallback or scheduleCallback
scheduleSyncCallback只有一个队列,将任务添加到队列里。按照顺序同步执行,不能中断。
function scheduleSyncCallback(callback: SchedulerCallback) { // callback =》performSyncWorkOnRootif (syncQueue === null) {syncQueue = [callback];} else {syncQueue.push(callback);}
} scheduleCallback有两个队列(小顶堆),timerQueue存放未就绪的任务,taskQueue存放已就绪任务。每次循环,判断timerQueue里是否有可执行任务,并将其移动到taskQueue中,然后从taskQueue中取出任务执行。
function unstable_scheduleCallback(priorityLevel, callback, options) {// ... startTime timeout expirationTime 等初始化var newTask = { // 新的调度任务id: taskIdCounter++,callback, // render时为performConcurrentWorkOnRoot.bind(null, root),priorityLevel,startTime, // getCurrentTime()expirationTime, // startTime + timeout(根据priorityLevel,-1、250、1073741823、10000、5000、)sortIndex: -1, // startTime > currentTime ? startTime: expirationTime,};// 按照是否过期将任务推到队列timerQueue或者taskQueue里if (startTime > currentTime) {newTask.sortIndex = startTime;push(timerQueue, newTask);if (peek(taskQueue) === null && newTask === peek(timerQueue)) {if (isHostTimeoutScheduled) {cancelHostTimeout(); // 取消当前的timeout} else {isHostTimeoutScheduled = true;}// 本质上是从timerQueue去取可以执行的任务放到taskQueue里,然后执行requestHostCallbackrequestHostTimeout(handleTimeout, startTime - currentTime);}} else {newTask.sortIndex = expirationTime;push(taskQueue, newTask);// 调度任务if (!isHostCallbackScheduled && !isPerformingWork) {isHostCallbackScheduled = true;requestHostCallback(flushWork); // 设置isMessageLoopRunning,开启宏任务【schedulePerformWorkUntilDeadline】(优先级:setImmediate > MessageChannel > setTimeout)执行 performWorkUntilDeadline()}}return newTask;
} 这里要注意下,一直以来都认为是
MessageChannel优先级大于setTimeout,但在浏览器打印之后发现事实相反。这个原因是chrome在某次更新里修改了二者的优先级顺序。想了解更多可以查看这篇文章:聊聊浏览器宏任务的优先级 - 掘金
执行任务 performWorkUntilDeadline
当监听到MessageChannel message的时候,执行该方法。通过调用scheduledHostCallback(即flushWork->workLoop返回的)结果,判断是否还有任务,若有则开启下一个宏任务。
const performWorkUntilDeadline = () => {if (scheduledHostCallback !== null) {const currentTime = getCurrentTime();startTime = currentTime;const hasTimeRemaining = true;let hasMoreWork = true;try {hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime); // scheduledHostCallback = flushWork ->workLoop} finally {if (hasMoreWork) {schedulePerformWorkUntilDeadline(); // 开启下一个宏任务MessageChannel,执行 performWorkUntilDeadline()} else {isMessageLoopRunning = false;scheduledHostCallback = null;}}} else {isMessageLoopRunning = false;}needsPaint = false;
}; workLoop
从taskQueue取出任务执行task.callback即(performConcurrentWorkOnRoot)。如果callback返回的是函数,则表示任务被中断。否则任务执行完毕,则弹出该任务。
function workLoop(hasTimeRemaining, initialTime) {let currentTime = initialTime;advanceTimers(currentTime); // 将 timerQueue里到时间执行的定时任务移动到 taskQueue 里currentTask = peek(taskQueue); // 从 taskQueue 取任务while (currentTask !== null &&!(enableSchedulerDebugging && isSchedulerPaused)) {// 任务未过期并且任务被中断if (currentTask.expirationTime > currentTime &&(!hasTimeRemaining || shouldYieldToHost())) {break;}const callback = currentTask.callback; // 在scheduleCallback接受的第二个参数:performConcurrentWorkOnRootif (typeof callback === 'function') {currentTask.callback = null;currentPriorityLevel = currentTask.priorityLevel;const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;// 如果返回是函数,则代表要重新执行;const continuationCallback = callback(didUserCallbackTimeout);currentTime = getCurrentTime();if (typeof continuationCallback === 'function') {// 任务暂停重新赋值callbackcurrentTask.callback = continuationCallback;} else {// 任务完成弹出if (currentTask === peek(taskQueue)) {pop(taskQueue);}}advanceTimers(currentTime); // 每次执行完,去timerQueue查看有没有到时间的任务} else {pop(taskQueue); // 弹出该任务}currentTask = peek(taskQueue);}// 返回给外部判断是否还有任务需要执行,即performWorkUntilDeadline里面的hasMoreWorkif (currentTask !== null) {return true;} else {// taskQueue里面没有任务了,从timerQueue取任务const firstTimer = peek(timerQueue);if (firstTimer !== null) {// 目的将timerQueue里的任务,移动到taskQueue里执行requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);}return false;}
} Render 阶段
这里render不是实际的dom render,而是fiber树的构建阶段。
Render入口
performSyncWorkOnRoot: 同步更新 =》 renderRootSync =》 workLoopSync
performConcurrentWorkOnRoot: 异步更新 =》 renderRootConcurrent =》 workLoopConcurrent
二者的区别主要是是否调用shouldYield,判断是否中断循环。
render之后就进入了commit阶段。
function performConcurrentWorkOnRoot(root, didTimeout) {currentEventTime = NoTimestamp;currentEventTransitionLane = NoLanes;const originalCallbackNode = root.callbackNode;const didFlushPassiveEffects = flushPassiveEffects();if (didFlushPassiveEffects) {// ...}let lanes = getNextLanes(root,root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,);if (lanes === NoLanes) {return null;}// 判断是否有用户输入、过期任务打断,需要同步渲染const shouldTimeSlice =!includesBlockingLane(root, lanes) &&!includesExpiredLane(root, lanes) &&(disableSchedulerTimeoutInWorkLoop || !didTimeout); // renderRootConcurrent|renderRootSync里都会调用prepareFreshStack:构建新的workInProgress树let exitStatus = shouldTimeSlice? renderRootConcurrent(root, lanes): renderRootSync(root, lanes);// render执行完成或抛出异常if (exitStatus !== RootInProgress) {if (exitStatus === RootErrored) {}if (exitStatus === RootFatalErrored) {}if (exitStatus === RootDidNotComplete) {markRootSuspended(root, lanes);} else {// render完成const renderWasConcurrent = !includesBlockingLane(root, lanes);const finishedWork: Fiber = (root.current.alternate: any);if (renderWasConcurrent &&!isRenderConsistentWithExternalStores(finishedWork)) {exitStatus = renderRootSync(root, lanes);if (exitStatus === RootErrored) {}if (exitStatus === RootFatalErrored) {}}// 将新的fiber树赋值给root.finishedWorkroot.finishedWork = finishedWork;root.finishedLanes = lanes;// 进入commit阶段->调用 commitRoot-> commitRootImpl;// commitRootImpl 执行完成之后会清空重置root.callbackNode和root.callbackPriority;以及重置workInProgressRoot、workInProgress、workInProgressRootRenderLanes。finishConcurrentRender(root, exitStatus, lanes); }}ensureRootIsScheduled(root, now()); // 退出前检测,是否有其他更新,需要发起调度if (root.callbackNode === originalCallbackNode) { // 没有改变,说明任务被中断,返回function,等待调用return performConcurrentWorkOnRoot.bind(null, root);}return null;
} 是否可中断循环
workLoopSync 和 workLoopConcurrent
共同点:用于构建fiber树,
workInProgress从根开始,遍历创建fiber节点。区别是:
workLoopConcurrent里面增加了shouldYield判断。
function workLoopSync() {while (workInProgress !== null) {performUnitOfWork(workInProgress);}
}function workLoopConcurrent() {while (workInProgress !== null && !shouldYield()) {performUnitOfWork(workInProgress);}
} 递归阶段 performUnitOfWork
遍历过程:从rootFiber向下采用深度优先遍历,当遍历到叶子节点时(递),然后会进入到归阶段,即遍历该节点的兄弟节点,如果没有兄弟节点则返回父节点。然后进行递归的交错执行。
递阶段
beginWork: 创建或复用fiber节点。diff过程在此发生;归阶段
completeWork: 由下至上根据fiber创建或复用真实节点,并赋值给fiber.stateNode;
function performUnitOfWork(unitOfWork: Fiber): void { // unitOfWork即workInProgress,指向下一个节点const current = unitOfWork.alternate;let next;next = beginWork(current, unitOfWork, renderLanes); unitOfWork.memoizedProps = unitOfWork.pendingProps;if (next === null) {// 遍历到叶子节点后,开始归阶段,并创建dom节点completeUnitOfWork(unitOfWork);} else {workInProgress = next; // workInProgress指向next}ReactCurrentOwner.current = null;
} 递归后的新的fiber树
Commit 阶段
通过commitRoot进入commit阶段。此阶段是同步执行的,不可中断。接下来经历了三个过程:
before mutation阶段(执行DOM操作前):处理DOM节点渲染/删除后的focus、blur逻辑;调用getSnapshotBeforeUpdate生命周期钩子;调度useEffect。
mutation阶段(执行DOM操作):DOM 插入、更新、删除
layout阶段(执行DOM操作后):调用类组件的
componentDidMount、componentDidUpdate、setState的回调函数;或函数组件的useLayoutEffect的create函数;更新ref。
页面渲染结果
import { useState } from 'react';export default function Count() {const [num, setNum] = useState(1);const onClick = () => {setNum(num + 1);};return (<div>num is {num}<button onClick={onClick}>点击+1</button></div>);
}function List() {const arr = [1, 2, 3];return (<ul>{arr.map((item) => (<li key={item}>{item}</li>))}</ul>);
}function App() {return (<div><Count /><List /></div>);
}export default App;
参考文章
[1] React https://github.com/facebook/react
[2] React技术揭秘 https://react.iamkasong.com/
[3] 图解React https://7km.top/main/macro-structure/
[4] 聊聊浏览器宏任务的优先级 https://juejin.cn/post/7202211586676064315
- END -
关于奇舞团
奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

相关文章:
启动 React APP 后经历了哪些过程
本文作者为 360 奇舞团前端开发工程师 前言 本文中使用的React版本为18,在摘取代码的过程中删减了部分代码,具体以源代码为准。 在React 18里,通过ReactDOM.createRoot创建根节点。并且通过调用原型链上的render来渲染。 本文主要是从以下两个…...
带自动采集小说网站源码 小说听书网站源码 小说网站源码 带教程
PTCMS可听书可下载的小说站源码 带自动采集和搭建视频教程 必装环境:Nginx(apache.iis也可),mysql,php5.6,memcached php5.6安装扩展memcache新建站点,注意新建时,PHP版本必须选择PHP5.6 安装教程 1.上传网站文件到网站目录&…...
MySQL学习笔记2
MySQL glibc版本安装: 下载相应的安装包。 学会查看mysql的官方文档: 1) 2)点击“Reference Manual”按钮: 3)选择5.7版本: 4)点击Installing MySQL on Unix/Linux Using Generic …...
【python爬虫】—星巴克产品
文章目录 需求爬取星巴克产品以及图片,星巴克菜单 python爬虫爬取结果 需求 爬取星巴克产品以及图片,星巴克菜单 网页分析: 首先,需要分析星巴克官方网站的结构,了解菜单栏的位置、布局以及菜单项的标签或类名等信息…...
算法 矩阵最长递增路径-(递归回溯+动态规划)
牛客网: BM61 求矩阵的最长递增路径 解题思路: 1. 遍历二维矩阵每个位置,max求出所有位置分别为终点时的最长路径 2. 求某个位置为终点的最长路径时,使用动态规划dp对已经计算出的位置进行记录 3. 处理某个位置的最长路径时,如果dp[i][j]位…...
四、数学建模之图与网络模型
1.定义 2.例题及软件代码求解 一、定义 1.图和网络是相关概念 (1)图(Graph):图是数学和计算机科学中的一个抽象概念,它由一组节点(顶点)和连接这些节点的边组成。图可以是有向的&…...
php在header增加key,sign,timestamp,实现鉴权
在PHP中,您可以通过在HTTP请求的Header中增加Key、Sign和Timestamp等信息来进行安全性鉴权。 以下是一种基本的思路和示例,用于说明如何实现这种鉴权机制: 生成Key和Sign: 服务端和客户端之间共享一个密钥(Key&#x…...
Spring实例化源码解析之ConfigurationClassParser(三)
前言 上一章我们分析了ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法的源码逻辑,其中核心逻辑do while中调用parser.parse(candidates)方法,解析candidates中的候选配置类。然后本章我们主要分析ConfigurationClassParser的…...
在 Substance Painter中实现Unity Standard Shader
由于有需要在Substance Painter中显示什么样的效果,在Unity就要显示什么样的效果的需求,最近研究了几天,总算在Substance Painter中实现Unity standard的材质的渲染效果。具体效果如下: 在Unity中: Substance Painte…...
第二证券:个人开证券账户要开户费吗?
随着互联网和移动端东西的遍及,越来越多的人开端涉足股票投资,开立证券账户也成为一个热门话题。但是,许多初学者或许会有疑问,个人开证券账户是否需求支付开户费呢?这个问题的答案并不是那么简略,需求考虑…...
大厂面试-16道面试题
1 java集合类有哪些? List是有序的Collection,使用此接口能够精确的控制每个元素的插入位置,用户能根据索引访问List中元素。常用的实现List的类有LinkedList,ArrayList,Vector,Stack。 ArrayList是容量…...
搭建GraphQL服务
js版 GraphQL在 NodeJS 服务端中使用最多 安装graphql-yoga: npm install graphql-yoga 新建index.js: const {GraphQLServer} require("graphql-yoga")const server new GraphQLServer({ typeDefs: type Query { hello(name:String):String! …...
数据仓库介绍及应用场景
数据仓库(Data Warehouse)是一个用于存储、管理、检索和分析大量结构化数据的集中式数据库系统。与传统的事务处理数据库不同,数据仓库是为了支持决策支持系统(Decision Support Systems, DSS)和业务智能(B…...
代码随想录算法训练营Day56 | 动态规划(16/17) LeetCode 583. 两个字符串的删除操作 72. 编辑距离
动态规划马上来到尾声了,当时还觉得动态规划内容很多,但是也这么过来了。 第一题 583. Delete Operation for Two Strings Given two strings word1 and word2, return the minimum number of steps required to make word1 and word2 the same. In on…...
HTML+CSS+JavaScript 大学生网页设计制作作业实例代码 200套静态响应式前端网页模板(全网最全,建议收藏)
目录 1.介绍2.这样的响应式页面这里有200套不同风格的 1.介绍 资源链接 📚web前端期末大作业 (200套) 集合 Web前端期末大作业通常是一个综合性的项目,旨在检验学生在HTML、CSS和JavaScript等前端技术方面的能力和理解。以下是一些可能的Web前端期末大…...
CFimagehost私人图床本地部署结合cpolar内网穿透实现公网访问
文章目录 1.前言2. CFImagehost网站搭建2.1 CFImagehost下载和安装2.2 CFImagehost网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道(云端设置)3.3.Cpolar稳定隧道(本地设置) 4.公网访问测…...
uniapp瀑布流布局写法
首先我们要清楚瀑布流是什么? 瀑布流布局(Waterfall Flow Layout),也称为瀑布流式布局,是一种常见的网页或移动应用布局方式,特点是元素以不规则的方式排列,就像瀑布中的流水一样,每…...
蓝桥杯 题库 简单 每日十题 day8
01 扫雷 题目描述 在一个n行列的方格图上有一些位置有地雷,另外一些位置为空。 请为每个空位置标一个整数,表示周围八个相邻的方格中有多少个地雷。 输入描述 输入的第一行包含两个整数n,m。 第2行到第n1行每行包含m个整数,相邻整…...
Keepalived 高可用(附带配置实例,联动Nginx和LVS)
Keepalived 一、Keepalived相关知识点概述1.1 单服务的风险(单点故障问题)1.2 一个合格的集群应该具备的特性1.3 VRRP虚拟路由冗余协议1.4 健康检查1.5 ”脑裂“现象 二、Keepalived2.1 Keepalived是什么?2.2 Keepalived体系主要模块及其作用…...
第二证券:今年来港股回购金额超700亿港元 9月近200家公司获增持
本年以来,港股上市公司回购力度不断增强。据恒生指数公司计算,到9月15日,本年以来港股回购金额到达735亿港元,占去年全年总额的70%。该公司预测,2023年港股回购金额可能到达929亿港元,是前5年年度平均水平的…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
