从 performWorkOnRoot 到 workInProgress tree:React 真正开始 render 的地方

上一篇我们讲到,scheduleUpdateOnFiber 并不会直接进入 beginWork,它只是把这次更新标记到 Root 上,然后通过 Root 调度系统决定后续怎么执行。

在 React 19 中,这条链路大概是:

复制代码
scheduleUpdateOnFiber
markRootUpdated
ensureRootIsScheduled
processRootScheduleInMicrotask
scheduleTaskForRootDuringMicrotask
performSyncWorkOnRoot 或 performWorkOnRootViaSchedulerTask
performWorkOnRoot

这一篇我们就从 performWorkOnRoot 开始讲。

也就是 React 真正进入 render 阶段的地方。

这篇要解决几个问题:

performWorkOnRoot 到底做了什么?

React 是怎么决定走同步渲染还是并发渲染的?

workInProgress tree 是什么时候创建的?

beginWork 是怎么被调用起来的?

初次渲染时,App 是在哪里变成 Fiber 的?

一、先明确 performWorkOnRoot 的位置

前面我们已经知道,React 19 中同步任务和并发任务会从不同入口进来。

同步任务一般会进入:

复制代码
performSyncWorkOnRoot(root, lanes)

并发任务一般会从 Scheduler 回调进入:

复制代码
performWorkOnRootViaSchedulerTask(root, didTimeout)

但是它们最终都会走到:

复制代码
performWorkOnRoot(root, lanes, forceSync)

所以 performWorkOnRoot 才是 render 阶段真正的总入口。

你可以这样理解:

scheduleUpdateOnFiber 负责告诉 React 有更新了。

ensureRootIsScheduled 负责把 Root 放进调度系统。

scheduleTaskForRootDuringMicrotask 负责判断这个 Root 应该怎么被调度。

performWorkOnRoot 才负责真正开始处理这个 Root 上的更新。

React 19 源码中,performWorkOnRootViaSchedulerTask 是通过 Scheduler 进入 React work loop 的入口,它会重新计算 lanes,然后调用 performWorkOnRoot(root, lanes, forceSync) 进入实际渲染流程。

二、performWorkOnRoot 不是直接 beginWork

很多人以为:

复制代码
performWorkOnRoot
beginWork
completeWork
commitRoot

这个理解太粗了。

真实流程中,performWorkOnRoot 在进入 beginWork 之前,还要做几件非常关键的事情:

复制代码
performWorkOnRoot
判断是否需要同步渲染
判断是否需要时间切片
调用 renderRootSync 或 renderRootConcurrent
prepareFreshStack
创建 workInProgress tree 的根节点
进入 workLoopSync 或 workLoopConcurrent
performUnitOfWork
beginWork
completeUnitOfWork
completeWork

所以 performWorkOnRoot 不是"遍历 Fiber 树"的函数。

它更像是 render 阶段的控制器。

它要先判断这次渲染怎么跑,然后才会进入真正的 work loop。

三、performWorkOnRoot 先决定同步还是并发

React 内部有两种 render 方式:

复制代码
renderRootSync

和:

复制代码
renderRootConcurrent

同步渲染就是一口气把 work loop 跑完,中途不会因为时间片耗尽而主动让出主线程。

并发渲染则会在 work loop 中检查是否应该让出主线程。

这就是 React 并发渲染的基础。

大概逻辑可以理解成:

复制代码
function performWorkOnRoot(root, lanes, forceSync) {
  const shouldTimeSlice =
    !forceSync &&
    !includesBlockingLane(lanes) &&
    !includesExpiredLane(root, lanes)

  const exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes)

  if (exitStatus !== RootInProgress) {
    commit 或继续处理异常、挂起、重试等情况
  }
}

这段伪代码表达的是核心思想,不是源码逐字复刻。

关键点在于:

React 不是只看你是不是 Concurrent Root。

它还要看当前 lanes 的优先级、是否过期、是否被强制同步执行。

也就是说,并发 Root 上也可能跑同步渲染。

比如:

用户触发了高优先级同步更新。

某些 lane 已经过期。

flushSync 强制同步执行。

这些情况下,即使 Root 支持并发能力,React 也可能走 renderRootSync

React work loop 中会根据是否应该 time slice,在 renderRootConcurrentrenderRootSync 之间选择;并发渲染并不是永远并发,它会受到 lanes、超时、强制同步等条件影响。

四、renderRootSync 和 renderRootConcurrent 的区别

这两个函数的目标是一样的:

从 Root 开始构建 workInProgress tree。

区别在于 work loop 的执行方式不同。

renderRootSync

同步渲染大概是这样:

复制代码
function renderRootSync(root, lanes) {
  prepareFreshStack(root, lanes)

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

  return workInProgressRootExitStatus
}

核心是:

复制代码
workLoopSync()

它会一直执行,直到没有下一个工作单元:

复制代码
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress)
  }
}

同步模式下,只要开始构建,就会一直往下跑,不主动让出主线程。

renderRootConcurrent

并发渲染大概是这样:

复制代码
function renderRootConcurrent(root, lanes) {
  prepareFreshStack(root, lanes)

  do {
    try {
      workLoopConcurrent()
      break
    } catch (thrownValue) {
      handleThrow(root, thrownValue)
    }
  } while (true)

  return workInProgressRootExitStatus
}

核心是:

复制代码
workLoopConcurrent()

它会在每个工作单元之间判断是否应该让出主线程:

复制代码
function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress)
  }
}

这就是你之前问的 shouldYield() 的意义。

workLoop 本身当然是在 JS 主线程上执行的。

如果一次 render 工作太多,React 不能一直霸占主线程,否则浏览器没有机会处理用户输入、动画、布局、绘制等任务。

所以并发渲染里,React 会把大的渲染任务拆成一个个 Fiber 工作单元。

每处理一个 Fiber,就检查一下:

现在还能继续干吗?

如果时间片用完了,就先停下来。

下次 Scheduler 再回调时,继续从上次停下的 workInProgress 开始干。

这就是并发渲染可中断、可恢复的基础。

五、prepareFreshStack:创建 workInProgress tree 的入口

不管是 renderRootSync 还是 renderRootConcurrent,在进入 work loop 之前,都要先调用:

复制代码
prepareFreshStack(root, lanes)

这个函数非常关键。

因为它会创建本轮 render 所使用的 workInProgress tree 的根节点。

大概逻辑是:

复制代码
function prepareFreshStack(root, lanes) {
  root.finishedWork = null
  root.finishedLanes = NoLanes

  workInProgressRoot = root
  workInProgressRootRenderLanes = lanes
  workInProgressRootExitStatus = RootInProgress

  workInProgress = createWorkInProgress(root.current, null)
}

重点是这一句:

复制代码
workInProgress = createWorkInProgress(root.current, null)

root.current 指向当前页面已经生效的 Fiber tree。

createWorkInProgress(root.current, null) 会基于 current tree 创建一棵新的 workInProgress tree 的根。

所以 workInProgress tree 不是凭空出现的。

它是从 current tree 克隆出来的。

不过这里要注意:

初次渲染时,也有 current tree。

只是这棵 current tree 非常空。

它只有一个 HostRootFiber,还没有真正的 App 子 Fiber。

所以初次渲染时:

复制代码
root.current

指向的是 HostRootFiber

它的 child 是 null。

然后 prepareFreshStack 会基于这个 HostRootFiber 创建对应的 workInProgress 版本。

也就是:

复制代码
current HostRootFiber
workInProgress HostRootFiber

接下来进入 beginWork 时,React 才会从 HostRootFiber.updateQueue 里拿到 payload.element,也就是 <App />,然后通过 reconcile 创建 App 对应的 Fiber。

所以你前面问的那个问题可以在这里得到准确答案:

App 不是在 root.render 时变成 Fiber 的。

也不是在 scheduleUpdateOnFiber 时变成 Fiber 的。

它是在 render 阶段处理 HostRootFiberbeginWork 时,通过 reconcileChildren 变成 Fiber 的。

六、current tree 和 workInProgress tree 到底是什么关系

React 内部始终维护两棵 Fiber tree:

复制代码
current tree

和:

复制代码
workInProgress tree

current tree 是当前屏幕上已经提交生效的 Fiber tree。

workInProgress tree 是本轮 render 正在计算的新 Fiber tree。

它们之间通过 alternate 互相连接。

复制代码
currentFiber.alternate === workInProgressFiber
workInProgressFiber.alternate === currentFiber

初次渲染时:

复制代码
current HostRootFiber
workInProgress HostRootFiber

这两个根 Fiber 已经通过 alternate 关联。

但是 App 对应的 Fiber 还没有。

因为 current tree 里还没有 App。

beginWork 处理 HostRootFiber 时,React 发现 updateQueue 里有:

复制代码
{
  element: <App />
}

于是会执行 reconcile:

复制代码
reconcileChildren(current, workInProgress, nextChildren, renderLanes)

这时候才会创建:

复制代码
App Fiber

并挂到:

复制代码
workInProgress.child

也就是挂到 workInProgress tree 上。

初次渲染时,current tree 的 HostRootFiber.child 还是 null。

workInProgress tree 的 HostRootFiber.child 会变成 App Fiber。

等 commit 完成后:

复制代码
root.current = finishedWork

workInProgress tree 就会变成新的 current tree。

这就是双缓存模型。

七、createWorkInProgress 做了什么

createWorkInProgress(current, pendingProps) 的作用是基于 current Fiber 创建或复用它的 alternate。

大概逻辑是:

复制代码
function createWorkInProgress(current, pendingProps) {
  let workInProgress = current.alternate

  if (workInProgress === null) {
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode
    )

    workInProgress.elementType = current.elementType
    workInProgress.type = current.type
    workInProgress.stateNode = current.stateNode

    workInProgress.alternate = current
    current.alternate = workInProgress
  } else {
    workInProgress.pendingProps = pendingProps
    workInProgress.flags = NoFlags
    workInProgress.subtreeFlags = NoFlags
    workInProgress.deletions = null
  }

  workInProgress.childLanes = current.childLanes
  workInProgress.lanes = current.lanes

  workInProgress.child = current.child
  workInProgress.memoizedProps = current.memoizedProps
  workInProgress.memoizedState = current.memoizedState
  workInProgress.updateQueue = current.updateQueue

  return workInProgress
}

这个函数有两个分支。

第一次创建 alternate

如果 current 没有 alternate,就创建一个新的 Fiber。

这通常发生在某个 Fiber 第一次拥有 workInProgress 对应节点时。

后续复用 alternate

如果 current 已经有 alternate,就复用它。

复用时会重置:

复制代码
flags
subtreeFlags
deletions

因为这些副作用标记属于上一轮 render,不能污染这一轮。

这也是 Fiber 架构性能优化的重要点:

React 不会每次都从零创建整棵树。

它会复用 alternate 结构,在 current 和 workInProgress 之间来回切换。

八、workInProgress 是一个全局游标

进入 render 阶段后,React 内部有一个非常关键的变量:

复制代码
workInProgress

它不是某个 Fiber 的属性,而是 React work loop 当前正在处理的 Fiber 指针。

可以把它理解成 DFS 遍历中的当前节点。

一开始:

复制代码
workInProgress = workInProgressRootFiber

也就是 workInProgress 版本的 HostRootFiber

然后 work loop 开始:

复制代码
while (workInProgress !== null) {
  performUnitOfWork(workInProgress)
}

每处理完一个 Fiber,performUnitOfWork 会返回下一个要处理的 Fiber。

如果当前 Fiber 有子节点,就进入子节点。

如果没有子节点,就 complete 当前节点,然后找兄弟节点。

如果没有兄弟节点,就一路向上 complete 父节点。

所以 React render 阶段本质上是一次深度优先遍历。

只不过它不是递归写法,而是用 workInProgress 这个全局游标实现的可中断遍历。

为什么不能简单用递归?

因为递归一旦开始,浏览器很难在中途恢复到某个精确的 Fiber 节点。

React 要支持并发渲染,就必须知道:

当前做到哪个 Fiber 了。

下次恢复时从哪里继续。

所以 Fiber 本身就是为可中断渲染设计的数据结构。

九、performUnitOfWork:每个 Fiber 的工作入口

work loop 每次都会调用:

复制代码
performUnitOfWork(unitOfWork)

它大概长这样:

复制代码
function performUnitOfWork(unitOfWork) {
  const current = unitOfWork.alternate

  let next = beginWork(current, unitOfWork, renderLanes)

  unitOfWork.memoizedProps = unitOfWork.pendingProps

  if (next === null) {
    completeUnitOfWork(unitOfWork)
  } else {
    workInProgress = next
  }
}

这个函数非常重要,因为它连接了两个阶段:

复制代码
beginWork
completeWork

你可以这样理解:

beginWork 是向下走。

completeWork 是向上归。

beginWork 返回子节点

如果当前 Fiber 处理完之后还有 child 需要继续处理,beginWork 会返回 child。

然后:

复制代码
workInProgress = next

work loop 下一轮就处理这个 child。

beginWork 返回 null

如果当前 Fiber 没有子节点,或者子节点不需要处理,beginWork 返回 null。

这时候说明不能继续向下了,要开始完成当前 Fiber。

于是进入:

复制代码
completeUnitOfWork(unitOfWork)

completeUnitOfWork 会调用 completeWork,然后找 sibling 或 return。

十、beginWork 是干什么的

beginWork(current, workInProgress, renderLanes) 的核心职责是:

根据当前 Fiber 类型,计算它的子 Fiber。

不同类型的 Fiber,处理方式不一样。

比如:

复制代码
HostRoot
FunctionComponent
ClassComponent
HostComponent
HostText
Fragment
SuspenseComponent
MemoComponent
ForwardRef

它们都会在 beginWork 里面进入不同分支。

大概结构是:

复制代码
function beginWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes)

    case FunctionComponent:
      return updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes)

    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes)

    case HostText:
      return null
  }
}

所以 beginWork 不是只负责函数组件。

它是所有 Fiber 类型进入 render 计算的分发入口。

React 的 beginWork 会根据 Fiber tag 分发到不同更新函数;work loop 通过 performUnitOfWork 调用 beginWork,如果返回子节点就继续向下,否则进入完成阶段。

十一、初次渲染时 App 是怎么变成 Fiber 的

这是这一篇最关键的部分。

以:

复制代码
root.render(<App />)

为例。

前面已经知道,root.render(<App />) 会创建一个 update:

复制代码
update.payload = {
  element: <App />
}

这个 update 会挂到:

复制代码
HostRootFiber.updateQueue

等到 render 阶段开始时,第一个被处理的 Fiber 是:

复制代码
workInProgress HostRootFiber

于是进入:

复制代码
beginWork(current, workInProgress, renderLanes)

因为它的 tag 是 HostRoot,所以会进入:

复制代码
updateHostRoot(current, workInProgress, renderLanes)

updateHostRoot 会处理 updateQueue。

处理完之后,会得到:

复制代码
nextChildren = <App />

然后执行:

复制代码
reconcileChildren(current, workInProgress, nextChildren, renderLanes)

也就是:

复制代码
reconcileChildren(
  currentHostRootFiber,
  workInProgressHostRootFiber,
  <App />,
  renderLanes
)

这时候 React 才真正根据 <App /> 这个 ReactElement 创建 App Fiber。

大概过程是:

复制代码
<App /> 是 ReactElement

reconcileSingleElement
createFiberFromElement
createFiberFromTypeAndProps

生成 App 对应的 Fiber

workInProgressHostRootFiber.child = appFiber
appFiber.return = workInProgressHostRootFiber

所以完整链路是:

复制代码
root.render(<App />)
创建 update
update.payload.element = <App />
update 挂到 HostRootFiber.updateQueue
scheduleUpdateOnFiber
performWorkOnRoot
renderRootSync 或 renderRootConcurrent
prepareFreshStack
workInProgress = workInProgress HostRootFiber
performUnitOfWork
beginWork HostRootFiber
updateHostRoot
processUpdateQueue
拿到 nextChildren,也就是 <App />
reconcileChildren
createFiberFromElement
创建 App Fiber
挂到 workInProgress.child

这才是 App 从 ReactElement 进入 Fiber 体系的准确位置。

十二、为什么 App 不在 root.render 时就变成 Fiber

因为 root.render 属于更新创建阶段。

它只负责表达:

我要把这个 element 渲染到这个 root 里。

它不会立即计算 Fiber 树。

原因有三个。

第一,React 需要先调度。

这次更新可能是同步的,也可能是并发的。

在调度系统决定执行之前,React 不应该直接开始构建 Fiber。

第二,React 需要批处理。

如果同一个事件里连续发生多次更新,React 会先把它们合并到 Root 上,而不是每次都立即构建一遍 Fiber tree。

第三,React 需要可中断渲染。

Fiber tree 的构建属于 render 阶段,它可能被暂停、恢复、重试、丢弃。

所以 ReactElement 到 Fiber 的转换,必须发生在 render work loop 里面,而不是 update 创建时。

这也是 Fiber 架构和老的同步递归渲染模型很大的区别。

十三、beginWork 为什么叫 begin

因为它只做当前 Fiber 的开始工作。

具体来说,它主要负责:

计算当前 Fiber 的新状态。

执行组件函数或处理 updateQueue。

生成或复用子 Fiber。

决定是否可以 bailout。

返回下一个要处理的子 Fiber。

它不负责创建真实 DOM。

真实 DOM 的创建主要发生在 completeWork 阶段。

比如对于原生节点:

复制代码
<div />

beginWork 阶段,React 只是创建或复用它的 Fiber 子节点。

到了 completeWork 阶段,才会为 HostComponent 创建真实 DOM 实例。

所以 render 阶段可以分成两个方向:

向下的 begin 阶段:

复制代码
beginWork
计算子 Fiber

向上的 complete 阶段:

复制代码
completeWork
创建 DOM 或收集副作用

这也是为什么 performUnitOfWork 里面先调用 beginWork

只有当当前节点没有 child 可以继续向下时,才开始 completeUnitOfWork

十四、workLoop 的遍历过程

假设组件结构是:

复制代码
function App() {
  return (
    <div>
      <Header />
      <Content />
    </div>
  )
}

初次渲染时,Fiber 构建过程大概是:

复制代码
HostRootFiber
App Fiber
div Fiber
Header Fiber
Content Fiber

work loop 的执行顺序大概是:

复制代码
beginWork HostRootFiber
创建 App Fiber

beginWork App Fiber
执行 App 函数组件
得到 div ReactElement
创建 div Fiber

beginWork div Fiber
处理 children
创建 Header Fiber 和 Content Fiber

beginWork Header Fiber
执行 Header 函数组件
创建 Header 的子 Fiber

如果 Header 没有更多 child
completeWork Header

beginWork Content Fiber
执行 Content 函数组件
创建 Content 的子 Fiber

completeWork Content

completeWork div
创建 div DOM,挂载子 DOM

completeWork App

completeWork HostRootFiber
render 阶段完成

这个过程是深度优先的。

先一路 begin 到最深。

然后 complete 当前节点。

再找兄弟节点。

兄弟节点处理完,再回到父节点 complete。

十五、completeUnitOfWork 是怎么向上归的

beginWork 返回 null 时,React 会进入:

复制代码
completeUnitOfWork(unitOfWork)

它大概做这几件事:

复制代码
function completeUnitOfWork(unitOfWork) {
  let completedWork = unitOfWork

  do {
    const current = completedWork.alternate
    const returnFiber = completedWork.return

    completeWork(current, completedWork, renderLanes)

    const siblingFiber = completedWork.sibling

    if (siblingFiber !== null) {
      workInProgress = siblingFiber
      return
    }

    completedWork = returnFiber
    workInProgress = completedWork
  } while (completedWork !== null)
}

它的逻辑是:

完成当前 Fiber。

如果有 sibling,就处理 sibling。

如果没有 sibling,就回到 parent。

一直往上归。

当最终归到 HostRootFiber,并且也没有更多 sibling 时,整棵 workInProgress tree 就构建完成了。

这时候:

复制代码
workInProgress = null

work loop 结束。

十六、render 阶段结束后得到了什么

render 阶段结束后,React 并没有马上修改页面。

它只是得到了一个计算完成的 workInProgress tree。

这个 tree 上有:

新的 memoizedProps。

新的 memoizedState。

新的 child Fiber 结构。

需要执行的 flags。

子树上的 subtreeFlags。

需要删除的 deletions。

对于初次渲染来说,很多 Fiber 会带有 Placement 标记。

表示这些节点需要在 commit 阶段插入到真实 DOM 中。

对于更新来说,可能会有:

复制代码
Update
Placement
ChildDeletion
Ref
Passive
Layout

等 flags。

所以 render 阶段的产物不是 DOM 更新本身,而是一份"待提交的变更计划"。

真正修改 DOM,要等 commit 阶段。

十七、为什么 render 阶段可以被中断,但 commit 阶段不行

这个问题很重要。

render 阶段做的是计算。

它在构建 workInProgress tree。

如果中途被打断,还没有影响真实页面。

所以它可以暂停、恢复、重试,甚至丢弃。

比如低优先级渲染做到一半,用户突然输入文字,React 可以先暂停低优先级任务,处理高优先级输入。

但是 commit 阶段不一样。

commit 阶段会真正修改 DOM。

一旦开始插入、删除、更新 DOM,就不能随便中断。

否则页面可能处在半更新状态。

所以 React 的并发能力主要发生在 render 阶段。

commit 阶段仍然是同步执行的。

这也是理解 Concurrent Rendering 的关键:

Concurrent 不是说 DOM 提交也可以被随意中断。

它主要是说 render 计算阶段可以被调度、暂停和恢复。

React 的并发渲染核心在于 render 阶段可中断,而 commit 阶段负责应用已经计算好的变更,需要保持同步一致性。

十八、这一篇的完整调用链

把这一篇串起来,React 19 中从调度进入 render 的主线是:

复制代码
performWorkOnRootViaSchedulerTask
或者 performSyncWorkOnRoot

performWorkOnRoot(root, lanes, forceSync)

判断 shouldTimeSlice

如果需要同步渲染
renderRootSync(root, lanes)

如果可以并发渲染
renderRootConcurrent(root, lanes)

prepareFreshStack(root, lanes)

workInProgress = createWorkInProgress(root.current, null)

进入 workLoopSync 或 workLoopConcurrent

performUnitOfWork(workInProgress)

beginWork(current, workInProgress, renderLanes)

如果是 HostRoot
updateHostRoot
processUpdateQueue
拿到 nextChildren
reconcileChildren
创建 App Fiber

如果是 FunctionComponent
updateFunctionComponent
renderWithHooks
执行函数组件
拿到 children
reconcileChildren

如果是 HostComponent
updateHostComponent
处理 props.children
reconcileChildren

如果 beginWork 返回 child
workInProgress = child
继续向下

如果 beginWork 返回 null
completeUnitOfWork
completeWork
找 sibling 或 return

直到 workInProgress === null

render 阶段完成
root.finishedWork = workInProgressRoot
进入 commitRoot

十九、这一篇最重要的结论

第一,performWorkOnRoot 是 React 真正进入 render 阶段的总入口。

第二,performWorkOnRoot 不会直接调用 beginWork,它会先决定本轮是同步渲染还是并发渲染。

第三,同步渲染走 renderRootSync,会一直执行 work loop,直到整棵 workInProgress tree 构建完成。

第四,并发渲染走 renderRootConcurrent,会在 work loop 中通过 shouldYield() 判断是否让出主线程。

第五,prepareFreshStack 会基于 root.current 创建本轮渲染的 workInProgress 根节点。

第六,初次渲染时,current tree 不是不存在,而是只有一个空的 HostRootFiber

第七,App 不是在 root.render 时变成 Fiber 的,而是在 beginWork HostRootFiber 时,通过 updateHostRootprocessUpdateQueuereconcileChildren 变成 App Fiber 的。

第八,work loop 是基于 workInProgress 指针实现的深度优先遍历,不是普通递归。

第九,beginWork 负责向下计算子 Fiber,completeWork 负责向上完成节点并收集副作用。

第十,render 阶段的产物是一棵 finished workInProgress tree,以及上面标记好的 flags,真正修改 DOM 要等 commit 阶段。

相关推荐
invicinble1 小时前
前端框架使用vue-cli( 第四层:业务源码层--登陆页相关)
前端·vue.js·前端框架
Rooting++1 小时前
vue2强制刷新路由的办法
前端·javascript·vue.js
nunumaymax1 小时前
【第四章-react ajax】
前端·react.js
前端若水1 小时前
层叠层(@layer):彻底解决优先级战争,告别 !important
前端·css·css3
invicinble1 小时前
前端框架使用vue-cli( 第二层:工程配置层--vue语法系列)
前端·vue.js·前端框架
爱滑雪的码农1 小时前
React+three.js之场景(Scene),相机(Camera)
前端·javascript·react.js
UXbot2 小时前
AI应用原型平台核心能力:界面自动生成、交互流程编辑、多格式代码导出详解
前端·低代码·交互·软件构建·原型模式·web app
call me by ur name2 小时前
多模态大模型轻量化
前端·网络·人工智能
Lee川2 小时前
登录注册模块的 JWT 认证机制详解
前端·后端·react.js