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

completeUnitOfWork


1 )概述

  • 各种不同类型组件的一个更新过程对应的是在执行 performUnitOfWork 里面的 beginWork 阶段
  • 它是去向下遍历一棵 fiber 树的一侧的子节点,然后遍历到叶子节点为止,以及 return 自己 child 的这种方式
  • performUnitOfWork 里面,还有一个方法叫做 completeUnitOfWork
  • completeUnitOfWork 中会根据是否有中断,调用不同的一个处理方法
  • 什么叫中断呢?
    • 就是我们在渲染一个组件的时候,出现了错误的场景进行处理
    • 或者优先级被更高优先级打断,低优先级转移到下一次处理
  • renderRoot 当中, 使用try catch去调用 workLoop 去循环每一个节点的
  • workLoop 里面调用的就是 performUnitOfWork
  • performUnitOfWork 里面调用了 beginWork
  • 它里面就是执行各种不同组件的一个update的一个过程
  • 在后面 completeUnitOfWork ,是处理到一侧的子节点遍历到最下层的时候,它没有子节点可以返回了
  • 因为 beginWork 里面调用了各种update,它们都是return自己的child
  • 比如说,有这么一棵 fiber 树的一个情况的时候
  • 我们先去对 RootFiber 执行了 beginWork,然后执行了之后 return 的是它的 child,也就是 App
  • 一层一层这么执行下来之后,到了有分叉节点,也就是说一个节点,下面有多个子节点的情况
  • 它 return 的还是它的第一个 child 节点,所以这个时候,后面的节点是没有被执行到更新的
  • 因为 inpput 它返回了之后,返回的是它里面的 input child 这个节点
  • 到这里为止,这个 dom 的 input 节点,已经没有child了,这个时候就 return null
  • 因为它的 child是 null,所以在这里首先执行的是 beginWork
  • 如果有child,它就会一直往child方向进行一个查找
  • 等到 next 等于 null 的情况的时候,就会去执行 completeUnitOfWork
  • completeUnitOfWork 就是对当前这个input界定 执行它的complete,具体执行什么
  • beginWork 的过程当中,如果我们去update某一个节点的时候,有报错了
  • 或者是 throw 了一个 promise, 就是 suspend 的情况, 它会执行一定的标记
  • renderRoot 当中,我们可以看到它如果有报错,它的catch是要在 renderRoot 里面被捕获的这个错误
  • 对这个错误补获之后,它是有一定的处理的, 处理完之后,它仍然处于这个 while 循环当中,它里面并没有 break
    • 位置: packages/react-reconciler/src/ReactFiberScheduler.js#L1276
  • 这个时候它还是会往下进行一个执行的,只不过它会在里面给我们当前这个节点去执行一个标记
  • completeUnitOfWork 的阶段,会对有各种不同标记的一个节点,执行不同的方法
  • 主要的是我们正常流程走的是叫 completeWork 这么一个方法
  • 而对于有异常的节点,它的一个流程是调用一个叫 unwindWork 的方法
  • 然后会判断是否有兄弟节点来执行不同的操作
    • 比如执行到上图 input 这个节点的 completeUnitOfWork之后,它没有兄弟节点
    • 它直接去执行它的父节点,也就是 Input 组件的 completeUnitOfWork
    • 在这里,我们发现它是有兄弟节点的,因为第一次update的过程,是往下的
    • 对于旁边的 List 节点是没有任何更新的, 这个时候我们就会返回它的兄弟节点 List
  • 对于这个兄弟节点, 继续对它执行一个update的一个过程,也就是 beginWork 的一个过程
  • 这就是在 completeUnitOfWork 里面非常重要的一个判断条件
    • 就是如果有兄弟节点,它会直接返回这个兄弟节点
  • 完成节点之后要赋值整个effect链
    • effect是非常重要的,后面即将要把所有的更新结束的节点,挂载到真正的dom上面
    • 这一个阶段叫做commit阶段,它要去执行每一个节点不同的跟dom有关的操作
    • 在前面 beginWorkcompleteUnitOfWork,对每一个节点的更新过程标记了
    • 有哪些 SideEffect 最终要被commit的一个过程
    • completeUnitOfWork 里面会完成这一步,把所有的effect节点进行一个串联
    • commitWork 的阶段可以非常方便的根据这个链去执行每一个节点的最终的操作

2 ) 源码

定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L939
找到 completeUnitOfWork

// 这个API 是在 workInProgress 的基础上进行的一个操作
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {// Attempt to complete the current unit of work, then move to the// next sibling. If there are no more siblings, return to the// parent fiber.// 进来首先就是一个 while true 的一个循环while (true) {// 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.// 对于这个循环, 它首先获取currentconst current = workInProgress.alternate;if (__DEV__) {ReactCurrentFiber.setCurrentFiber(workInProgress);}// 然后 获取 returnFiber 和 siblingFiber,也就是它的父节点以及它的兄弟节点// 这个在后面我们判断是否要返回兄弟节点的时候就会用到const returnFiber = workInProgress.return;const siblingFiber = workInProgress.sibling;// 这里首先一进来就有一个大的判断,它是一个整个方法里面最大的一个判断// Incomplete 就是这个节点它是出现了错误,然后被捕获的, 并标记这个 sideEffect// 逻辑与操作来判断某一个属性上面它是否有某一个特性的一个方式if ((workInProgress.effectTag & Incomplete) === NoEffect) {if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {// Don't replay if it fails during completion phase.mayReplayFailedUnitOfWork = false;}// This fiber completed.// Remember we're completing this unit so we can find a boundary if it fails.nextUnitOfWork = workInProgress;if (enableProfilerTimer) {if (workInProgress.mode & ProfileMode) {startProfilerTimer(workInProgress);}nextUnitOfWork = completeWork(current,workInProgress,nextRenderExpirationTime,);if (workInProgress.mode & ProfileMode) {// Update render duration assuming we didn't error.stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);}} else {nextUnitOfWork = completeWork(current,workInProgress,nextRenderExpirationTime,);}if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {// We're out of completion phase so replaying is fine now.mayReplayFailedUnitOfWork = true;}stopWorkTimer(workInProgress);resetChildExpirationTime(workInProgress, nextRenderExpirationTime);if (__DEV__) {ReactCurrentFiber.resetCurrentFiber();}if (nextUnitOfWork !== null) {// Completing this fiber spawned new work. Work on that next.return nextUnitOfWork;}// 接下去这个个判断,是要构造一个所有 有 SideEffect 的节点的一个链状的结构// 这个链状结构最终是用于 commitWork 的时候用来进行对这些有 SideEffect的节点进行 commit 的一个操作,// 这边它的一个判断条件,returnFiber不等于null,并且returnFiber它不是一个 Incomplete 的一个节点// 因为对于一个 Incomplete 的节点,它唯一可以具有的一个SideEffect,就是这个节点已经被捕获了// 因为对于有 Incomplete 错误的节点是不会渲染正常的子节点的if (returnFiber !== null &&// Do not append effects to parents if a sibling failed to complete(returnFiber.effectTag & Incomplete) === NoEffect) {// Append all the effects of the subtree and this fiber onto the effect// list of the parent. The completion order of the children affects the// side-effect order.// 对于正常的一个情况, 首先要判断一下 returnFiber.firstEffect 是否等于 null// 符合判断就代表 现在这个 returnFiber 上还没有记录任何它的子节点的有副作用的子节点// 这个时候, 直接把当前节点的firstEffect赋值给 returnFiber.firstEffect// 因为它之前是没有任何一个的嘛,我们这边真正要做的是把当前节点的 firstEffect 到 lastEffect的一个链条// 这个单项链表,给它挂载到它的父节点的同样的一个 firstEffect到lastEffect的单项链表的最后if (returnFiber.firstEffect === null) {returnFiber.firstEffect = workInProgress.firstEffect;}// 就是下面这一段判断,就是来做这个事情的, 如果returnFiber.lastEffect不等于null,那说明它已经有了// 那么对于returnFiber上面有记录过别的 SideEffect 的节点之后// 我们当前节点是挂载到整个 SideEffect 链的最后,就是下面这样,就是把它连到最后的上面if (workInProgress.lastEffect !== null) {if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;}// 还要操作如下,因为它的这个(returnFiber.lastEffect)指针目前还指向它原来的那个lastEffect// 在 赋值 nextEffect之后,它的最后一个就是这个链的最后一个已经变成 workInProgress.lastEffect,所以这边要执行这么一个操作// 当然这个条件是要建立在 workInProgress.lastEffect 是有值的情况// 这是把它们各自的firsteffect到lasteffect,这个链给它进行一个串联的过程returnFiber.lastEffect = workInProgress.lastEffect;}// If this fiber had side-effects, we append it AFTER the children's// side-effects. We can perform certain side-effects earlier if// needed, by doing multiple passes over the effect list. We don't want// to schedule our own side-effect on our own list because if end up// reusing children we'll schedule this effect onto itself since we're// at the end.// 对于returnFiber来说,当前这个节点也可能是有副作用的,那么这边就接下去就会做这个操作// 如果当前节点的 effectTag  > PerformedWork 的,因为 PerformedWork 是一个给 DEVTool 用的一个 sideEffect// 对于真正的react更新是没有任何意义的, 所以如果它仅仅只有 PerformedWork ,它就不是一个有效的 SideEffect 的节点const effectTag = workInProgress.effectTag;// Skip both NoWork and PerformedWork tags when creating the effect list.// PerformedWork effect is read by React DevTools but shouldn't be committed.if (effectTag > PerformedWork) {// 如果它有 SideEffect,就把当前节点作为父节点的 SideEffect 链的最后一个给它挂载上去// 或者如果是当前父节点没有任何记录的 SideEffect,它就是第一个if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = workInProgress;} else {returnFiber.firstEffect = workInProgress;}returnFiber.lastEffect = workInProgress;}}if (__DEV__ && ReactFiberInstrumentation.debugTool) {ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);}if (siblingFiber !== null) {// If there is more work to do in this returnFiber, do that next.return siblingFiber;} else if (returnFiber !== null) {// If there's no more work in this returnFiber. Complete the returnFiber.workInProgress = returnFiber;continue;} else {// We've reached the root.return null;}} else {if (enableProfilerTimer && workInProgress.mode & ProfileMode) {// Record the render duration for the fiber that errored.stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);// Include the time spent working on failed children before continuing.let actualDuration = workInProgress.actualDuration;let child = workInProgress.child;while (child !== null) {actualDuration += child.actualDuration;child = child.sibling;}workInProgress.actualDuration = actualDuration;}// This fiber did not complete because something threw. Pop values off// the stack without entering the complete phase. If this is a boundary,// capture values if possible.const next = unwindWork(workInProgress, nextRenderExpirationTime);// Because this fiber did not complete, don't reset its expiration time.if (workInProgress.effectTag & DidCapture) {// Restarting an error boundarystopFailedWorkTimer(workInProgress);} else {stopWorkTimer(workInProgress);}if (__DEV__) {ReactCurrentFiber.resetCurrentFiber();}if (next !== null) {stopWorkTimer(workInProgress);if (__DEV__ && ReactFiberInstrumentation.debugTool) {ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);}// If completing this work spawned new work, do that next. We'll come// back here again.// Since we're restarting, remove anything that is not a host effect// from the effect tag.next.effectTag &= HostEffectMask;return next;}if (returnFiber !== null) {// Mark the parent fiber as incomplete and clear its effect list.returnFiber.firstEffect = returnFiber.lastEffect = null;returnFiber.effectTag |= Incomplete;}if (__DEV__ && ReactFiberInstrumentation.debugTool) {ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);}// 存在 sibling 节点, 注意这边是return,也就是跳出while循环if (siblingFiber !== null) {// If there is more work to do in this returnFiber, do that next.return siblingFiber;} else if (returnFiber !== null) {// If there's no more work in this returnFiber. Complete the returnFiber.// 如果returnFiber不等于null,那么 workInProgress = returnFiber// 比如对于最上面示例图的 input节点,它的 completeUnitOfWork// 如果它没有兄弟节点,那么它就继续执行Input的 completeUnitOfWork// 这个循环是在 completeUnitOfWork 内部进行的一个过程workInProgress = returnFiber;continue;} else {// 如果 returnFiber 也等于null,那么它直接 return null// 说明已经到达顶点了,就到达 RootFiber 了,我们的更新过程呢已经完成了// 只需要在接下去 commitRoot 就可以了return null;}}}// Without this explicit null return Flow complains of invalid return type// TODO Remove the above while(true) loop// eslint-disable-next-line no-unreachablereturn null;
}
  • 对于这个while循环里面,其实它的所有代码都是在if else里面的
  • if else它们最大的一个区别是什么呢?
    • 在if里面我们可以看这边这个特殊情况
    • 就是 nextUnitOfWork 等于 completeWork,然后传入一些属性
    • 对于 else 的情况,这边调用了 next = unwindWork
    • 这就是对于一个是否有抛出过错误的一个节点,执行的一个不同的操作
  • completeWork,是没有任何一个错误的一个节点,它的一个complete的一个过程
    • 调用了completework,就是把这个节点的更新给它完成了
  • unwindWork 是有节点的错误,被捕获,如何去处理的一个过程
  • 在上面的 SideEffect 到底有什么意义呢?
  • 比如有如下场景:
    • 有个列表上有值为 1, 2, 3的三个节点
    • 点击 button 按钮,会让上面三个节点内的值乘上自身
    • 对于2,3是4和9,对于1还是1
    • 这里 1 是一个没有变化的过程
    • 按照正常逻辑来说,只需要更新原本值是 2 和 3 的节点
    • 实际上 react 里面也是这样做的
  • 参考demo如下
    import React, { Component } from 'react'
    import './App.css'class List extends Component {state = {a: 1,b: 2,c: 3,}handleClick = () => {this.setState(oldState => {const { a, b, c } = oldStatereturn {a: a * a,b: b * b,c: c * c,}})}render() {const { a, b, c } = this.statereturn [<span key="a">{a}</span>,<span key="b">{b}</span>,<span key="c">{c}</span>,<button key="button" onClick={this.handleClick}>click me</button>,]}
    }class Input extends Component {state = {name: 'wang',}handleChange = e => {// 这里如果使用方法设置`state`// 那么需要现在外面读取`e.target.value`// 因为在React走完整个事件之后会重置event对象// 以复用event对象,如果等到方法被调用的时候再读取`e.target.value`// 那时`e.target`是`null`this.setState({name: e.target.value,})}render() {return (<inputtype="text"style={{ color: 'red' }}onChange={this.handleChange}value={this.state.name}/>)}
    }class App extends Component {render() {return (<div className="main"><Input /><List /></div>)}
    }export default App
    
  • 这个时候我们在 浏览器中针对 react-dom.development.js 在 commitRoot 上面打了一个断点
  • 这个断点是看最终要进行commit的时候,它是如何去获取哪几个节点是要更新的
  • 也就是我们的 SideEffect 链是怎么样的一个形式, 点一下,断点被捕获,捕获了之后
  • 可以看到它接收的两个参数,一个是root,一个是finishedWork,那么它们其实是一个对应的关系
    • root.current.alternate 就等于 finishedWork
    • 因为 finishedWork 对应的是 workInProgress
    • finishedWork 上面就会记录我们当前这一次更新,它所有需要去执行的,就是要更新到dom上面的一些内容
      • 它的记录就在 firstEffect 到 lastEffect 的一个链上
      • 它这边 firstEffect 记录的 elementType 是一个 span
      • 所谓 effect 它最终是一个fiber节点,就是告诉我们哪一个fiber节点需要更新
      • 对于我们这边第二个span,它的值是要被更新成4的
      • 所以可以看到它的 firstEffect 的第一个节点是 span
      • 它的 effectTag是 4,然后它的 updateQueen 的属性里面是一个数组 updateQueue: ["children", "4"]
      • 这个大致意思可以看出,我们要把这个span标签对应的dom节点, 它的children里面显示的文字内容,从2变成4
      • 还有就是下一个节点, 也是一个span, 看 nextEffect, 它的 elementType 也是span
      • 它的 updateQueen 是 updateQueue: ["children", "9"],也就是原值为 3 的节点
  • 对于commitWork,只会关心 firstEffect 到 lastEffect 这个链条上面的对应的 fiber 节点
    • 它只需要更新的就是这两个dom节点的children的一个变化
  • 这就是react里面的 vdom,它以最小化的程度去更新 dom
  • 而不需要对其他的任何节点再进行一个操作,以此来提升一个性能
  • 这个过程, 就是在 v16.6.3/packages/react-reconciler/src/ReactFiberScheduler.js#L1026 这个 if 里面被实现的
  • 这个 demo 示例对应最最上面的流程图来说
    • 这边点了 button之后,在这里创建了一个update
    • 就是这个List上面, 通过调用 this.setState 去创建了这个 update
    • 最终是要从 RootFiber 上面往下进行一个更新,更新到 List 中的第一个span,以及后续的span
    • 然后发现只有第2和第3个span,它们的 children从 2 变成了 4,从 3 变成了 9
    • 它们两个对应的 SideEffect 是 Update 对应值为4 (二进制的表示形式, 参考 ReactSideEffectTags.js),它们要把内容挂载到dom上面
    • 第一个span的内容因为是1乘以1,所以它没有变化,就不需要执行真正的更新
    • 首先执行 completeUnitOfWork 的是 第一个 span, 接下去它的 sibling
    • 因为第一个 span 没有 SideEffect,在这里,它的 returnFiber 就是 List
    • 当前的 workInProgress 就是这第一个 span,这个span没有任何的 SideEffect
    • 这个 List 自己也没有 SideEffect,所以赋值List的时候,对List也是没有任何的一个更新的, 如下
      if (returnFiber.firstEffect === null) {returnFiber.firstEffect = workInProgress.firstEffect;
      }
      
    • 这时候的 List 本身的 firstEffect 到 lastEffect是没有任何内容的,也就是null
    • 进行到第2个span节点,它自己有 SideEffect, 而它没有子节点
    • 所以它本身上面的 firstEffect到lastEffect是没有任何内容的
    • 唯一需要操作的是要把它作为List的第一个 SideEffect 增加到这个链上面
    • 因为是 Update, 值为 4 肯定是大于 Performedwork 值为 1的
      if (effectTag > PerformedWork) {if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = workInProgress; // 情况1,标记为 L1, 以便下面引用说明} else {returnFiber.firstEffect = workInProgress; // 情况2, 标记为 L2}returnFiber.lastEffect = workInProgress; // 通用设定3,标记为 L3
      }
      
    • 它自身(workInProgress)要作为 returnFiber 的第一个 SideEffect
    • 给它增加到 List 的firstEffect到lastEffect的链上
    • 所以returnFiber的 firstEffect 和 lastEffect,都等于这个Fiber对象(第二个span) workInProgress
    • 对应的执行语句是 L2L3
    • 当第2个span执行complete完了,又要开始它的sibling, 也就是第三个span
    • 第三个span也是有更新的,所以它也要增加到 List上面
    • 这个时候List上面已经有变化,因为List.lastEffect(姑且这样表示)已经不等于null了
    • 这个时候,执行的是 else 的情况,即 L1
    • 因为在上一个节点的complete过程当中,已经指定了 firstEffect 和 lastEffect 都为 workInProgress(第2个span)
    • 所以第二个span节点的 nextEffect 指向的是第三个span的节点
    • 它们更新完之后又要更新 button,因为它没有任何的变化,所以 button 也没有更新
    • 这个时候对于List来说它的 SideEffect 链是等于 span指向span的这么一个过程
    • List的firstEffect指向第二个span对应的Fiber对象,lastEffect指向第三个span对应的Fiber对象
    • List本身执行 completeUnitOfWork 的时候,自己没有任何的更新
    • 因为它的state更新对于真正的dom操作,没有任何的关系,而且它也没有生命周期方法
    • 所以 List 后期没有任何要做的事情了, 自己本身是没有 SideEffect,只有子节点的 SideEffect
    • 在 List 执行 completeUnitOfWork 的时候, 代码中的 returnFiber 对应是 div
    • 而 workInProgress 对应的是 List, 而 List 要赋值它的 firstEffect 和 lastEffect
    • 它的链指向returnFiber, 就是要放到 returnFiber 它的链的最后面
    • returnFiber 这个 div,它目前是肯定没有任何的 SideEffect的, 所以它直接赋值成 workInProgress.firstEffect
    • 也就是说,对于 div 它的 firstEffect 跟 lastEffect 已经变成 List 的 firstEffect 跟 lastEffect
    • 因为上面节点都是没有任何更新的, 所以一层一层往上之后, 在 RootFiber 上面记录的
    • firstEffect 跟 lastEffect 也就变成了最开始的两个span
    • 所以这就是通过 completeUnitOfWork,最终能够赋值到 RootFiber 上面
    • 在它的 firstEffect 到 lastEffect,单项链表上面去记录整个应用当中
    • 所有需要去更新的最终的dom节点以及组件的fiber对象的一个过程
    • 这里涉及到后期 commitRoot 的时候, 调用这个单项链表一个过程,这里先跳过
  • 对于 unwindWork 的一个过程, 也是相似的, 先跳过 unwindWork 以及 completeWork
  • 目前只关心它的一个遍历过程,跳过具体节点做的事情, 关注它的大致流程
  • 它真正帮我们去实现了把所有需要更新的节点进行一个串联
  • 让最终 commitRoot 的时候,能够方便获取到所有需要更新的节点的一个过程

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

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

相关文章

Kotlin程序设计 扩展篇(一)

Kotlin程序设计&#xff08;扩展一&#xff09; **注意&#xff1a;**开启本视频学习前&#xff0c;需要先完成以下内容的学习&#xff1a; 请先完成《Kotlin程序设计》视频教程。请先完成《JavaSE》视频教程。 Kotlin在设计时考虑到了与Java的互操作性&#xff0c;现有的Ja…

像素图片在网页中很模糊怎么办?输入这个样式

像素图片在网页中很模糊怎么办&#xff1f;输入这个样式 image-rendering: pixelated;输入前 输入后

业务逻辑漏洞—验证码绕过

验证码绕过第一关&#xff1a; 前端验证码绕过&#xff1a; 打开pikachu靶场&#xff1a; 输入错误的验证码时会出现弹窗&#xff08;alert&#xff09;此时我们猜测这可能存在着前端限制 如果验证码有前端限制&#xff08;只在前端有作用&#xff09;&#xff0c;不影响后…

(十一)Head first design patterns状态模式(c++)

状态模式 如何去描述状态机&#xff1f; 假设你需要实例化一台电梯&#xff0c;并模仿出电梯的四个状态&#xff1a;开启、关闭、运行、停止。也许你会这么写 class ILift{ public:virtual void open(){}virtual void close(){}virtual void run(){}virtual void stop(){} }…

基于SpringBoot Vue二手闲置物品交易系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

4小时精通MyBatisPlus框架

目录 1.介绍 2.快速入门 2.1.环境准备 2.2.快速开始 2.2.1引入依赖 2.2.2.定义Mapper ​编辑 2.2.3.测试 2.3.常见注解 ​编辑 2.3.1.TableName 2.3.2.TableId 2.3.3.TableField 2.4.常见配置 3.核心功能 3.1.条件构造器 3.1.1.QueryWrapper 3.1.2.UpdateWra…

Flowable 加签和减签

一&#xff1a;示例 Deployment deploy repositoryService.createDeployment().name("会签流程").addClasspathResource("processes/CounterSignProcess.bpmn").deploy();ProcessInstance processInstance runtimeService.startProcessInstanceByKey(&qu…

【操作系统】同步和互斥详细讲解(算法+源码)

博主介绍&#xff1a;✌全网粉丝喜爱、前后端领域优质创作者、本质互联网精神、坚持优质作品共享、掘金/腾讯云/阿里云等平台优质作者、擅长前后端项目开发和毕业项目实战✌有需要可以联系作者我哦&#xff01; &#x1f345;附上相关C语言版源码讲解&#x1f345; &#x1f44…

记录一次从有道云笔记迁移到语雀笔记

推荐阅读 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;一&#xff09; 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;二&#xff09; 1、安装git&#xff0c;python3等准备工作 文章中标注python3&#xff0c;为避免与python2 冲…

【Java-框架-SpringSecurity】单点登录(认证和授权)- 随笔

项目文件&#xff1b; 【1】 【2】 【3】 【4】 【5】 【6】 【7】 【8】

【小白学机器学习3】关于最简单的线性回归,和用最小二次法评估线性回归效果, 最速下降法求函数的最小值

目录 1 什么是回归分析 1.1 什么是线性回归 1.2非线性回归 2 数据和判断方法 2.1 原始数据 2.2 判断方法&#xff1a;最小二乘法 3 关于线性回归的实测 3.1 用直线模拟 3.2 怎么判断哪个线性模拟拟合更好呢&#xff1f; 3.2.1 判断标准 3.2.2 最小二乘法 3.2.3 高维…

【征服redis16】收官-redis缓存一致性问题解决方案

今天我们来写redis最后一篇&#xff1a;redis作为缓存时如何与数据库实现数据一致的问题。 最近看redis看得有点麻了&#xff0c;这篇就简单描述吧 目录 1.什么是缓存与数据库一致性问题 1.1 缓存一致性的概念 1.2 缓存不一致的场景 2.缓存不一致的解决思路 1.什么是缓存…

HarmonyOS 通过Web组件嵌套网络应用

我们今天来说说 在程序中嵌套一个网址地址 HarmonyOS中是通过一个简单的WEB组件来实现 网络应用就是相当于网址地址 通过链接将应用嵌入到手机当中 WEB组件需要两个参数 一个是 src 地址 要嵌套的网址 另一个是 控制器 我们可以先编写代码如下 import webview from "o…

力扣hot100 环形链表 快慢指针 计步器

Problem: 141. 环形链表 文章目录 思路&#x1f496; 快慢指针法&#x1f496; 计步器法 思路 &#x1f468;‍&#x1f3eb; 参考题解 &#x1f496; 快慢指针法 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( 1 ) O(1) O(1) /*** Definition for singly-linked list…

【QT+QGIS跨平台编译】之五:【curl+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、curl介绍二、curl下载三、文件分析四、pro文件五、编译实践 一、curl介绍 curl&#xff08;CommandLine Uniform Resource Locator&#xff09;主要功能就是用不同的协议连接和沟通不同的服务器&#xff0c;相当封装了的socket。 libcurl支持http, https, ftp, g…

大模型实战营Day5笔记

大模型部署背景 大模型部署是指将训练好的模型在特定的软硬件环境中启动的过程&#xff0c;使模型能够接收输入并返回预测结果。大模型的内存开销巨大&#xff0c;7B模型仅权重需要14G内存。另外大模型是自回归生成&#xff0c;需要缓存Attention的 k/v。 LMDeploy 简…

超简单的OCR模块:cnocr

前言 毫无疑问的是&#xff0c;关于人工智能方向&#xff0c;python真的十分方便和有效。 这里呢&#xff0c;我将介绍python众多OCR模块中一个比较出色的模块&#xff1a;cnocr 模块介绍 cnocr是一个基于PyTorch的开源OCR库&#xff0c;它提供了一系列功能强大的中文OCR模型和…

小型园区组网实例

目录 拓扑需求IP规划路由配置交换机配置NAT配置ACL配置DHCP配置配置过程&#xff1a;配置结果&#xff1a; OSPF配置链路聚合配置配置过程&#xff1a; 网络测试 拓扑 需求 企业网络信息服务平台需实现功能&#xff1a;企业网站服务器、FTP服务器、DNS服务器。企业ip分配地址段…

阿里巴巴开源联邦学习框架FederatedScope

5月5日&#xff0c;阿里巴巴达摩院发布新型联邦学习框架FederatedScope&#xff0c;声称可以在不共享训练数据的情况下开发机器学习算法&#xff0c;从而保护隐私。&#xff0c;其源代码现已在Apache 2.0许可下发布在GitHub上。 介绍 该平台被描述为一个全面的联邦学习框架&a…