React 原理

Next.js 原理

react原理

Question 1 React 中 state 更新之后,组件为什么不会立刻同步更新?


你需要至少提到 3 个核心点:

  1. React 不会立即更新 state,而是“收集更新”
  2. 批处理(batching)
  3. 调度 / 异步更新(不是同步 setState)

标准面试回答(中文)

React 中 state 更新之后不会立刻同步更新,主要是因为 React 采用了异步更新和批处理机制(batching)来优化性能

当我们调用 setStateuseState 的更新函数时,React 并不会马上去重新渲染组件,而是会:

  1. 先把这次更新放到一个更新队列(update queue)中
  2. 在同一个事件循环中收集多个更新
  3. 最后统一进行一次重新渲染

这样做的好处是:避免多次重复渲染,提高性能

另外,在 React 18 之后,引入了更强的 调度机制(scheduler)和并发特性(concurrent features),React 可以根据任务优先级决定什么时候更新,而不是立即执行。

 

Standard Answer (English)

In React, state updates are not applied immediately because React uses asynchronous updates and batching for performance optimization.

When you call setState or a state updater function, React does not re-render the component right away. Instead, it:

  1. Queues the update in an update queue
  2. Collects multiple updates within the same event loop
  3. Processes them together in a single render

This helps avoid unnecessary multiple re-renders and improves performance

Starting from React 18, React also introduced a more advanced scheduler and concurrent features, allowing it to prioritize updates instead of executing them immediately.

 

Question 2 React 的 batching(批处理)在 React 18 前后有什么区别?

 

👉 提示一下方向:


标准面试回答(中文)

在 React 18 之前,批处理(batching)只在 React 的事件处理函数中生效,比如点击事件、onChange 等。

也就是说:

例如在 React 17 中:

👉 会触发 两次渲染

 

而在 React 18 中,引入了 自动批处理(automatic batching)

👉 无论是在事件、setTimeout、Promise 还是原生事件中,都会自动批处理

所以上面的代码在 React 18 中:

👉 只会触发 一次渲染


口语化答案:

  1. React 18 之前:半自动 (Limited Batching)

中文: “在 React 18 之前,批处理是有局限的。它只在 React 自己的事件回调函数(比如 onClickonChange)里起作用。但如果你把 setState 放在 setTimeoutPromise 或者原生 DOM 事件里,React 就‘管不着’了,它会立刻同步更新,改几次就渲染几次。”

English: "Before React 18, batching was limited. It only worked inside React event handlers like onClick or onChange. However, if you put setState inside a setTimeout, a Promise, or a native DOM event, React couldn't batch them. It would update the state synchronously, triggering a re-render for every single setState call."

  1. React 18 之后:全自动 (Automatic Batching)

中文: “到了 React 18,引入了自动批处理(Automatic Batching)。现在的 React 变聪明了,不管你在哪里调用 setState——哪怕是在异步的 fetch 请求或者定时器里——它都会自动把它们攒在一起,最后只做一次渲染。这大大提升了复杂应用的性能。”

English: "In React 18, they introduced Automatic Batching. Now, React is much smarter. No matter where you call setState—even inside asynchronous code like fetch requests or timers—React will automatically group them together and perform just one single re-render. This is a huge performance boost for complex apps."

  1. 特殊情况:如何退出批处理? (The "Escape Hatch")

中文: “当然,如果你在极少数情况下非要立刻更新(比如更新完立刻要读取 DOM 尺寸),React 18 提供了一个 flushSync 函数,让你可以手动退出批处理,强制立刻同步渲染。”

English: "Of course, if you ever have a rare case where you need an immediate update (like reading DOM dimensions right after a change), React 18 provides the flushSync function. It allows you to opt out of batching and force a synchronous re-render."

 

 

Question 3 (开始进入原理 update queue)

React 是如何知道哪些 state 需要更新的?(update queue 是怎么工作的)


标准面试回答(中文)

React 是通过 Fiber 节点上的 update queue(更新队列) 来管理 state 更新的。

当我们调用 setStateuseState 时,并不是直接修改 state,而是:

1️⃣ 创建一个 update 对象

这个对象会记录:

 

2️⃣ 放入 update queue(链表结构)

每个组件对应一个 Fiber,每个 Fiber 上都有一个update queue(本质是一个链表)

多个 setState 会变成这样:

 

3️⃣ 渲染阶段(render phase)统一处理

React 在下一次 render 时,会遍历这个链表,把所有 update 依次计算一遍,得到最终 state

例如:

React 会:

👉 最终只渲染一次,state = 2

 

4️⃣ 为什么这样设计?

👉 合并多次更新,避免重复渲染,提高性能

同时配合 React 18:

👉 可以根据“优先级”决定哪些 update 先执行(调度机制)


口语化回答:

  1. 状态存哪了? (Where is state stored?)

中文: “首先要明确,state 其实并不在你的函数组件内部,而是存在 React 底层的 Fiber 节点上。每个组件实例在 React 内存里都有一个对应的 Fiber 对象,里面有一个单向链表叫 memoizedState。你每调用一次 useState,React 就会在这个链表里存一个 Hook 对象。”

English: "First, state doesn't actually live inside your functional component; it's stored in React's internal Fiber nodes. Each component instance has a corresponding Fiber object in memory with a linked list called memoizedState. Every time you call useState, React adds a Hook object to this list."

  1. React 怎么知道哪个 state 对应哪个变量? (How does it match them up?)

中文: “React 是靠 ‘调用顺序’ 来定位的。因为每次渲染时,Hooks 的执行顺序必须固定,React 就像拨动刻度盘一样,第一个 useState 对应链表的第一个节点,第二个对应第二个。当你调用 setState 时,这个函数内部已经闭包绑定了它对应的那个 Fiber 节点和 Hook 对象,所以 React 知道该更新谁。”

English: "React relies on the order of Hook calls. Since the order must remain the same every render, React matches the first useState to the first node in the linked list, the second to the second, and so on. When you call setState, the function is already bound to its specific Fiber node via a closure, so React knows exactly which state to update."

  1. 怎么判断‘需要更新’? (How does it detect a change?)

中文: “React 使用 Object.is (严格相等) 来比对。如果你传入的是对象或数组,但引用没变(直接修改原对象),React 会认为值没变,从而跳过更新。这就是为什么我们强调必须传一个 新对象(Shallow Copy),因为 React 只看‘地址’变没变,不看里面的内容。”

English: "React uses Object.is (strict equality) to compare the old and new values. If you're updating an object or array but keep the same reference, React assumes nothing has changed and skips the re-render. That's why we always use shallow copies—React checks the memory address, not the deep content."

 

 

Question 4(非常高频 + 容易挂 为什么useState直接修改不会触发更新)

为什么 useState 直接修改 state 不会触发更新?

比如:

👉 为什么页面不会更新?


标准面试回答(中文)

直接修改 state(比如 state.count = 1)不会触发更新,是因为:

👉 React 并不会监听对象内部的变化,而是通过“引用变化(reference change)”来判断是否需要更新。


核心原因拆解

1️⃣ React 不做“深度监听”

React 不像一些框架(比如 Vue)会做数据劫持(proxy / defineProperty)。

👉 React 是:

不关心对象里面变了什么,只关心“这个对象是不是变了”

 

2️⃣ useState 的更新机制

当你调用:

React 会做一件事:对比新旧 state 的引用(浅比较)

如果:

👉 React 认为:没变 → 不更新

 

3️⃣ 直接修改的问题

这个操作:

👉 所以不会触发重新渲染

 

4️⃣ 正确写法

👉 这里创建了一个新对象(新引用)

React 检测到:

👉 触发更新


口语化回答:

  1. 它是“订阅通知制”,而不是“实时监控制” (Notification vs. Observation)

中文: “React 并不是在 24 小时监控你的变量。它只有在你调用 setCount 或者 setState 这种‘派发函数’时,才会收到通知说:‘嘿,我该去准备更新了’。如果你直接写 state.count = 1,你只是改了内存里的一个值,React 压根不知道你动过它,所以它也就不会去启动重新渲染的流程。”

English: "React doesn't constantly watch your variables for changes. It works on a notification system. It only knows it needs to re-render when you explicitly call a setter function like setCount or setState. If you just do state.count = 1, you're changing a value in memory, but you're not 'notifying' React, so it simply doesn't know an update is needed."

  1. 引用地址没变,React 会“跳过”更新 (Referential Equality & Bailing Out)

中文: “即使你后面补了一个 setCount,但如果你传的是同一个对象引用,React 也会‘罢工’。React 内部使用 Object.is 进行浅比较。如果你直接修改了原对象,它的内存地址(引用)还是同一个。React 发现‘新旧地址没变’,就会认为数据没变,直接跳过(Bail out)渲染来节省性能。”

English: "Even if you call a setter later, React might still skip the update if the reference hasn't changed. React uses Object.is for a shallow comparison between the old and new state. If you mutate the original object, its memory address (reference) stays the same. React sees that the references are identical, assumes nothing has changed, and 'bails out' of the re-render to save performance."

  1. 破坏了“不可变性” (Breaking Immutability)

中文: “React 提倡不可变性 (Immutability)。每次状态更新都应该是生成一份全新的‘快照’。直接修改旧状态会破坏 React 的更新追踪机制,甚至可能导致一些奇奇怪怪的 Bug,比如调试工具里的状态对不上,或者 useEffect 的依赖项失效。”

English: "React relies on Immutability. Every state update should result in a brand-new 'snapshot'. Direct mutation messes up React's internal tracking and can lead to bugs where the UI is out of sync with the data, or useEffect dependencies fail to trigger correctly."

 

Question 5(Hooks 原理关键题)

useState 为什么可以在函数组件中“记住状态”?

👉 换句话说:

函数每次执行都会重新运行,那 state 是怎么“保存下来”的?


标准面试回答(中文)

useState 能在函数组件中“记住状态”,本质原因是:

👉 React 在 Fiber 上维护了一条 hooks 链表,每次渲染按顺序读取这些 hooks。


核心原理拆解

1️⃣ 不是靠函数本身保存状态

函数组件每次执行:

👉 实际上是重新执行一遍函数

所以:

❌ 不是函数记住了 state ❌ 也不只是闭包(很多人会说错)

 

2️⃣ 状态存在哪里?

👉 存在 Fiber 节点上

每个组件都有一个 Fiber:

👉 这是一条 链表(linked list)

 

3️⃣ useState 做了什么?

当你写:

React 内部会:

  1. 找到当前组件对应的 Fiber
  2. 在 hooks 链表中找到当前这个 hook(靠顺序!)
  3. 返回对应的 state

 

4️⃣ 为什么 hooks 不能写在 if 里?

👉 会破坏“顺序”

React 是这样取的:

如果顺序变了:

👉 React 就会读错 state(错位)

 

5️⃣ setState 做了什么?

👉 本质是:


口语化回答:

  1. 组件不是孤立运行的 (Components aren't running in a vacuum)

中文: “虽然函数组件每次渲染都会重新运行,但 React 内部为每个组件维护了一个 Fiber 节点(长久存在的对象)。这个 Fiber 节点就像是组件的‘档案袋’,它在组件挂载期间是一直存在的,不会随着函数运行结束而消失。”

English: "Even though the functional component re-runs every time it renders, React maintains a Fiber node for each component instance. Think of this Fiber node as a 'file folder' for the component that stays in memory as long as the component is mounted. It doesn't disappear when the function finishes executing."

  1. 状态存在“备忘录”链表里 (Stored in the memoizedState linked list)

中文: “在 Fiber 节点上有一个属性叫 memoizedState。React 把你在这个组件里用到的所有 Hooks 按顺序串成一个单向链表存放在这里。当你调用 useState 时,React 其实是去这个链表里对应的位置‘找’之前存下的值,而不是重新创建一个值。”

English: "There’s a property on the Fiber node called memoizedState. React stores all the Hooks used in that component as a linked list. When you call useState, React isn't creating a new state from scratch; it's actually looking up the stored value at the specific position in that linked list."

  1. 依靠执行顺序来“取回”状态 (Retrieval based on execution order)

中文: “这也是为什么 Hooks 不能写在循环或条件判断里。因为 React 只能靠‘这是第几个 Hook’来对号入座。只要你调用的顺序不变,React 就能确保第二次渲染时,第一个 useState 拿到的还是第一次渲染存进去的那个值,从而实现了‘记住状态’的效果。”

English: "This is exactly why Hooks cannot be called inside loops or conditions. React relies on the execution order to match the state to the right variable. As long as the order stays the same, React guarantees that the first useState call will retrieve the exact same value stored during the previous render."

 

Question 6(核心基础 · 必问 Fiber)

什么是 Fiber?React 为什么要引入 Fiber?

👉 你可以先试着回答哪怕一句,比如:


可以从这几个角度想:

  1. React 以前有什么问题?
  2. 为什么需要“可中断”?
  3. Fiber 和“链表 / 树”有没有关系?

标准面试回答(中文)

Fiber 是 React 16 引入的一种新的架构(reconciliation architecture),本质上是一种数据结构(链表形式的树),用于描述组件的更新过程。

👉 引入 Fiber 的核心目的:

让 React 的渲染过程变成“可中断、可恢复、可优先级调度”的


详细解析:

为什么需要 Fiber?(这是面试重点)

❌ 在 Fiber 之前(React 15)

React 使用的是:

👉 递归调用(stack reconciliation)

问题是:


✅ Fiber 做了什么?

👉 把“递归”改成“可拆分任务”

也就是说:

React 可以:


🧩 Fiber 的本质结构

每个组件对应一个 Fiber 节点,结构类似:

👉 这其实是一个:

用链表实现的树结构(方便遍历 & 中断)


⚙️ Fiber 带来的能力(必须记住3个)

1️⃣ 可中断(interruptible)

👉 渲染过程中可以暂停

 

2️⃣ 可恢复(resumable)

👉 下次从中断的地方继续

 

3️⃣ 优先级调度(scheduler)

👉 高优先级任务(用户输入)优先执行


口语化回答:

  1. 什么是 Fiber? (What is Fiber?)

中文: “简单来说,Fiber 就是一种数据结构,也是 React 最新的协调(Reconciliation)引擎。以前 React 的更新是递归的,一旦开始就停不下来。而 Fiber 把每一个组件的更新任务拆成了一个个微小的‘单元’。在代码层面,它就是一个普通的 JavaScript 对象,存储了组件的类型、对应的 DOM、以及我们之前聊过的 memoizedState 链表。”

English: "Simply put, Fiber is both a data structure and React's core reconciliation engine. In the past, React's updates were recursive and unstoppable. Fiber breaks down the rendering work into tiny 'units of work.' At the code level, it’s just a plain JavaScript object that stores information about the component type, its DOM node, and the state linked list."

  1. 为什么要引入 Fiber? (Why did React introduce it?)

核心痛点:解决“卡顿” (The Main Pain Point: Blocking the Main Thread)

中文: “引入 Fiber 最核心的原因是为了实现可中断的渲染(Incremental Rendering)。在 Fiber 之前,如果组件树非常大,React 更新起来会长时间霸占浏览器主线程,导致用户的点击、动画等操作得不到响应,界面就会‘卡死’。”

English: "The primary reason for Fiber was to enable incremental rendering. Before Fiber, React would hog the main thread for a long time if the component tree was huge. This meant user interactions like clicks or animations couldn't be processed, making the UI feel 'frozen' or 'janky'."

  1. Fiber 带来的改变:从“一跑到底”到“急诊室模式”

中文: “有了 Fiber 后,React 拥有了优先级调度的能力。它就像一个高效的调度员:如果正在渲染一个大列表,用户突然点了一下按钮,React 能够暂停当前的渲染,先去处理用户点击这种高优先级的任务,等主线程空闲了再回来继续渲染。这种‘见缝插针’的更新方式,让应用在高负载下依然能保持流畅。”

English: "With Fiber, React gained priority-based scheduling. It acts like a dispatcher: if it's rendering a huge list and the user suddenly clicks a button, React can pause the current work, handle the high-priority user interaction first, and then resume the rendering when the main thread is idle. This 'cooperative multitasking' keeps the app smooth even under heavy loads."

 

Question 7 (render阶段和commit阶段)

React 为什么要分 render 阶段和 commit 阶段?


标准面试回答(中文)

React 把更新过程分成两个阶段:

 

1️⃣ Render 阶段(也叫 Reconciliation 阶段)

👉 做什么:计算“要不要更新”

在这个阶段,React 会:

⚠️ 关键点:

❗ 这个阶段 不操作真实 DOM

👉 所以它是:

 

2️⃣Commit 阶段

👉 做什么:真正“把变化应用到 DOM”

在这个阶段,React 会:

⚠️ 关键点:

❗ 这个阶段必须是同步的,不能中断

原因很简单:

👉 DOM 更新如果中断,会导致 UI 不一致


为什么要分两阶段?(核心逻辑)

如果不分:

👉 React 一边算 diff,一边改 DOM

问题:

 

👉 分成两阶段后:

Render 阶段:

👉 可以慢慢算、可以暂停

Commit 阶段:

👉 一口气更新 DOM(保证一致性)


和 Fiber 的关系(关键串联)

Fiber 的作用就是: 让 render 阶段可以被拆分成小任务

所以:


口语化回答:

  1. 明确分工:计算 vs. 执行 (Calculation vs. Execution)

中文: “React 把更新过程分开,核心是为了效率和一致性Render 阶段是找不同(Diffing),它是纯计算的,React 会算出哪些地方需要改;Commit 阶段是真干活(Dom Mutation),它会把计算结果一次性应用到真实的 DOM 上。这种分工让 React 可以在真正动 DOM 之前,有余地去进行优化。”

English: "React splits the process into two phases for efficiency and consistency. The Render phase is all about calculation—it's where React performs 'Diffing' to figure out what needs to change. The Commit phase is the actual execution, where React applies those changes to the real DOM all at once. This separation gives React the flexibility to optimize before touching the browser."

  1. 为了实现“可中断”更新 (Enabling Interruptible Rendering)

中文: “这是引入 Fiber 后的关键。Render 阶段是异步且可以被中断的。 如果计算到一半,发现有更高优先级的任务(比如用户点击),React 可以直接丢弃当前的进度先去忙别的。如果 Render 阶段直接操作了 DOM,那中断时界面就会显示一半旧、一半新的内容,直接‘崩了’。分阶段确保了只有当全部计算完、确保没问题后,才进入同步且不可中断的 Commit 阶段一次性更新。”

English: "This is the key to Fiber. The Render phase is asynchronous and interruptible. If a high-priority task like a user click comes in, React can pause or discard the current calculation. If we manipulated the DOM during the Render phase, an interruption would leave the UI in a half-baked, inconsistent state. By separating them, React ensures that it only enters the synchronous, uninterruptible Commit phase once the entire update is ready."

  1. 性能优化与批量提交 (Batching & Atomic Updates)

中文: “分阶段还能保证原子性。在 Render 阶段,React 可能会反复计算好几次(比如 Concurrent 模式下),但它只会触发一次 Commit。这样就避免了频繁操作 DOM 导致的布局抖动(Layout Thrashing),让页面看起来更丝滑。”

English: "It also ensures atomic updates. React might recalculate several times during the Render phase (especially in Concurrent Mode), but it will only trigger one single Commit. This prevents layout thrashing caused by frequent DOM manipulations, making the UI feel much smoother."

 

Question 8(高频核心 useEffect在render还是commit阶段执行?)

useEffect 是在 render 阶段执行还是 commit 阶段执行?为什么?


标准答案(中文)

👉 useEffect 是在 commit 阶段之后执行的(异步执行)


正确执行顺序是:

React 更新流程是:

 

关键点(面试核心)

useEffect 不是 render,也不是 commit 内执行。它是在:DOM 更新完成之后,浏览器绘制之后执行。


为什么要这样设计?

因为如果在 render 阶段执行 ❌

 

如果在 commit 阶段直接执行 ❌

 

所以 React 选择:

👉 commit 完 + 浏览器绘制后,再异步执行 effect


和 useLayoutEffect 的区别(高频追问)

Hook执行时机是否阻塞绘制
useLayoutEffectcommit 后、paint 前❌ 会阻塞
useEffectpaint 后✅ 不阻塞

useEffect 在 commit 阶段之后、浏览器绘制完成后异步执行。

useEffect runs after the commit phase and after the browser has painted.

  1. 为什么不在 Render 阶段执行? (Why not in the Render phase?)

中文: “绝对不能在 Render 阶段执行。因为 Render 阶段是纯净的(Pure),它的唯一任务是计算 UI 结构。而 useEffect 通常用来处理副作用,比如发请求、手动操作 DOM。如果放在 Render 阶段,由于渲染可能被 React 暂停或丢弃,副作用就会变得不可控,导致逻辑混乱甚至内存泄漏。”

English: "It definitely cannot run in the Render phase. The Render phase must be pure, as its only job is to calculate the UI structure. useEffect is designed for side effects like data fetching or manual DOM manipulation. If we ran them during Render, which React can pause or discard at any time, the effects would become unpredictable, leading to bugs or memory leaks."

  1. 为什么在 Commit 阶段之后异步执行? (Why asynchronous after the Commit phase?)

中文:useEffect 会在浏览器完成布局(Layout)和绘制(Paint)之后再执行。这是为了不阻塞浏览器渲染。如果我们在 Commit 阶段同步执行耗时的副作用,用户就会感觉到明显的掉帧或卡顿。React 的哲学是:先把新界面给用户看,然后再去跑那些不紧急的副作用代码。”

English: "Specifically, useEffect runs asynchronously after the browser has finished layout and paint. This is to avoid blocking the browser's painting. If we ran heavy side effects synchronously during the Commit phase, the user would experience frame drops or lag. React's philosophy is: show the new UI to the user first, then run the non-urgent side effects."

  1. 补充:那 useLayoutEffect 呢? (What about useLayoutEffect?)

中文: “如果你确实需要在 DOM 更新后、浏览器绘制前做点什么(比如测量 DOM 尺寸并立刻修改样式),那就得用 useLayoutEffect。它是同步执行的,会阻塞绘制,所以要慎用,否则页面会闪烁或卡顿。”

English: "If you actually need to do something after the DOM is updated but before the browser paints (like measuring DOM size and adjusting styles immediately), that’s what useLayoutEffect is for. It runs synchronously and blocks the paint, so it should be used sparingly to avoid performance issues."

 

Question 9(高频 + 难点 useEffect的依赖数组)

useEffect 为什么会有依赖数组?依赖是怎么被 React 记录和比较的?


标准面试回答(中文)

useEffect 的依赖数组,本质是 React 用来判断副作用是否需要重新执行的一种“浅比较机制”

 

1️⃣ React 如何“记住依赖”?

React 在内部会为每个 useEffect 保存一份数据:

👉 上一次的依赖数组(prevDeps)

结构类似:

 

2️⃣ 下一次 render 时做什么?

当组件重新 render:

React 会拿:

做比较:

👉 逐个用 Object.is 比较

 

3️⃣ 判断规则

 

4️⃣ 依赖数组三种情况

① 没有依赖数组

👉 每次更新都执行

② 空依赖数组

👉 只在首次挂载执行

③ 有依赖

👉 count 变化才执行

 

5️⃣ 为什么必须“浅比较”?

React 不做深比较,因为:

👉 所以只比较:

引用是否变化(Object.is)

面试加分点(很重要)

👉 React 判断依赖变化本质是:

基于 Fiber 上保存的 previous deps,通过 Object.is 做浅比较


口语化回答

  1. 为什么要依赖数组? (Why the dependency array?)

中文: “核心目的是为了控制副作用的执行时机。如果没有依赖数组,useEffect 每次渲染都会跑一遍,这会导致严重的性能浪费甚至死循环(比如在 Effect 里更新了 state)。有了它,React 就可以通过‘对比’来决定:到底是要重新跑一遍这个函数,还是沿用上次的结果。”

English: "The main purpose is to control when the effect runs. Without a dependency array, useEffect would run after every single render, which is a huge waste of performance and can even lead to infinite loops if you update state inside the effect. The array tells React: 'Only re-run this function if these specific values have changed.'"

  1. React 是怎么记录这些依赖的? (How does React store them?)

中文: “这又回到了我们之前聊的 Fiber 节点。React 在存储这个 Hook 的时候,不仅存了那个副作用函数,还会把当前的依赖数组存进这个 Hook 的 memoizedState 里。它就像是一份‘快照备份’,专门留着给下次更新时做参考。”

English: "This goes back to the Fiber node. When React stores this Hook, it doesn't just save the effect function; it also saves the dependency array into the Hook’s memoizedState. Think of it as a 'snapshot backup' that React keeps as a reference for the next render."

  1. 依赖是怎么被比较的? (How are they compared?)

中文: “React 会对数组里的每一项进行 浅比较(Shallow Comparison),底层用的就是 Object.is。它会遍历当前的数组和上次备份的数组,只要其中任何一项的‘引用地址’变了,React 就会认为依赖已更新,从而重新执行 Effect。这也就是为什么如果你在依赖项里放了一个在函数体内新声明的对象,Effect 每次都会跑,因为每次渲染那个对象的引用都是新的。”

English: "React performs a shallow comparison using Object.is on each element in the array. It loops through the current dependencies and the previous ones; if even one element's reference has changed, React marks the effect as 'dirty' and re-runs it. This is why if you put an object created inside the component body into the dependencies, the effect will run every time, because that object gets a fresh memory address on every render."

 

 

Question 10 (为什么if语句里面不能使用hooks)

React 为什么不能在 if 语句中使用 hooks?底层到底发生了什么问题?


标准面试回答(中文)

React 不能在 if / for / while 中调用 Hooks 的根本原因是:Hooks 在 React 内部是通过“调用顺序(index 顺序)”来存储和读取的

1️⃣ React 是怎么存 Hooks 的?

每个组件对应一个 Fiber,Fiber 上有一条 hooks 链表:

React 并不是用名字找 hook,而是:

👉 按调用顺序一个一个取

 

2️⃣ 正常情况(稳定顺序)

React 内部会记录:

👉 顺序完全固定

 

3️⃣ 如果写在 if 里

问题来了:

情况 A:flag = true

情况 B:flag = false

结果是什么?

👉 React 会“错位读取 hooks”

导致:

 

4️⃣ 本质原因(面试核心一句)

👉 React hooks 依赖的是:

稳定的调用顺序,而不是条件判断

 

5️⃣ 为什么 React 不用“名字映射”?

因为:


口语化回答:

  1. 核心机制:依靠顺序定位 (The Mechanism: Order-based Indexing)

中文: “底层最根本的原因是,React 并不是通过‘名字’来识别 Hook 的,而是靠‘执行顺序’。正如之前提到的,每个组件的 Hooks 都存放在 Fiber 节点的一个单向链表里。每次渲染时,React 会从链表头开始,按顺序把状态一个一个分发给对应的 Hook 调用。”

English: "The fundamental reason is that React doesn't identify Hooks by name; it identifies them by their execution order. As we discussed, a component’s Hooks are stored in a linked list on the Fiber node. During every render, React starts from the head of that list and distributes the state to each Hook call one by one, based on their position."

  1. 底层发生了什么? (What happens under the hood?)

中文: “如果你把 Hook 写在 if 语句里,一旦某次渲染条件不成立,这个 Hook 就不执行了。这会导致链表的索引完全对不上 想象一下,链表里本来存着 [StateA, StateB, EffectC]。如果你跳过了第一个 useState,React 依然会从链表第一个位置拿数据给当前的第一个 Hook 调用。结果就是:第二个 Hook 拿到了第一个 Hook 的状态,第三个拿到了第二个的……整个组件的数据流就全部‘串路’了。”

English: "If you put a Hook inside an if statement and the condition fails during a render, that Hook is skipped. This messes up the pointer in the linked list. Imagine the list stores [StateA, StateB, EffectC]. If you skip the first useState, React will still pull the data from the first slot and give it to whatever Hook is called first. Consequently, the second Hook gets the first Hook's state, the third gets the second's, and the entire data flow becomes misaligned."

  1. 结果:崩溃或不可预测的 Bug (The Result: Crashes or Bugs)

中文: “这不仅会导致数据错误,还可能导致 React 直接崩溃。比如 React 预期下一个是 useEffect,结果你调用的是 useState,类型对不上,React 就会报错。为了从源头上规避这种‘灾难’,React 才规定了 Hooks 必须在顶层调用。”

English: "This doesn't just cause data bugs; it can crash the app. If React expects a useEffect but gets a useState call instead, the internal Hook types won't match, and React will throw an error. To prevent this 'catastrophe' at the source, React enforces the rule that Hooks must be called at the top level."

 

最后可以加上一句:

“这就像在自动取款机排队。银行(React)不看脸,只看你是队列里的第几个。如果排在第一的人(Hook 1)突然悄悄溜走了,那本来属于他的钱就会错发给原本排在第二的人(Hook 2)。为了保证大家都能领到正确的钱,所有人必须保持队列整齐,不准中途离场。”

 

 

Question 11(进阶核心 Concurrent Rendering)

React 18 的 Concurrent Rendering(并发渲染)到底解决了什么问题?


标准面试答案(中文)

React 18 的 Concurrent Rendering(并发渲染)的核心目标是:

让 React 的渲染过程变成可中断、可调度的任务,从而提升 UI 响应性,避免长时间渲染阻塞主线程。

 

1️⃣ 旧架构的问题(React 17 及之前)

在传统 React 中:

👉 问题:

 

2️⃣ Concurrent Rendering 做了什么

React 18 引入并发机制后:

👉 把渲染变成“可中断任务”

React 可以:

 

3️⃣ 核心能力

✔️ Time slicing(时间切片)

把渲染拆成小块执行,而不是一次性执行完。

✔️ Priority scheduling(优先级调度)

不同任务有优先级:

✔️ 可丢弃渲染

低优先级更新可以被新的更新覆盖。

 

4️⃣ 解决的问题

 

一句话总结(必须背)

👉 中文:

Concurrent Rendering 让 React 渲染过程可中断并支持优先级调度,从而提升页面响应速度与流畅度。


要抓住两个关键词:“可中断(Interruptible)”“优先级(Priority)”

 

口语化回答:

  1. 核心痛点:不可中断的“巨型任务” (The Pain Point: Blocking Tasks)

中文: “在 React 18 之前,渲染过程是‘一站到底’的。一旦 React 开始渲染,主线程就会被完全占满,直到整个组件树渲染完成。如果这个过程耗时很长(比如一个有几千项的列表),用户在这期间点击按钮、在输入框打字,浏览器都是没反应的,给人的感觉就是‘卡死了’。”

English: "Before React 18, rendering was an 'all-or-nothing' process. Once React started rendering, it would hog the main thread until the entire tree was finished. If you had a heavy task, like rendering a list with thousands of items, user interactions like clicks or typing would be completely blocked, making the UI feel 'frozen'."

  1. 并发解决了什么?:把主线程还给用户 (The Solution: Giving the Thread Back)

中文: “并发渲染并不是说‘同时跑多个任务’,而是让渲染变得可中断。React 会把庞大的渲染任务拆成很小的时间片。在渲染的间隙,React 会去询问浏览器:‘现在有高优先级的用户操作吗?’ 如果有,React 就暂停当前的渲染,先让浏览器处理用户的点击或输入,等用户操作完了,再回来继续渲染。这解决了‘长任务阻塞主线程’的问题。”

English: "Concurrent rendering doesn't mean running multiple tasks at the exact same time; it means making rendering interruptible. React breaks down large rendering tasks into small time slices. Between these slices, React 'checks in' with the browser: 'Is there a high-priority user action?' If so, React pauses the current render to let the browser handle the click or input first, then resumes once the thread is free. This solves the issue of long tasks blocking the main thread."

  1. 引入了“优先级”的概念 (Introducing Priority)

中文: “并发模式还赋予了开发者手动标注优先级的能力。比如通过 startTransition,你可以告诉 React:‘这个搜索结果列表的更新不那么紧急,你可以慢慢画;但用户的输入框文字必须立刻更新’。这样 React 就能优先保证界面的交互流畅,而不是为了加载数据让整个页面卡住。”

English: "It also gives developers the power to manually define priorities. With startTransition, you can tell React: 'Updating this search results list isn't urgent—take your time—but the input field text must update immediately.' This allows React to prioritize a smooth interactive experience over heavy background updates."

 

形象的总结:

“以前的 React 像个固执的厨师,不把这一桌的大餐做完,绝不去给新来的客人点菜;现在的并发 React 像个聪明的服务型厨师,他会在炒菜的间隙停下来,先帮新客人点个饮品,然后再回去接着炒菜。虽然菜做的总时长没变,但客人的等待体验变好了。”

 

 

Question 12(核心进阶 react scheduler)

React Scheduler 是什么?它和 Concurrent Rendering 是什么关系?


1️⃣ Scheduler 是什么?

React Scheduler 是 React 内部的任务调度系统,它的作用是:

👉 决定“哪个任务先执行、什么时候执行、能不能被打断”

它本质上做三件事:

 

2️⃣ 它解决什么问题?

在没有 Scheduler 的情况下:

Scheduler 的目标:

👉 让 React 可以“分时执行任务”,避免阻塞浏览器渲染和用户交互

 

3️⃣ Scheduler 和 Concurrent Rendering 的关系

👉 两者关系是:

可以理解为:

 

4️⃣ React 18 的核心升级点

Scheduler + Fiber + Lane Model:

👉 实现了:


  1. React Scheduler 是什么? (What is Scheduler?)

中文: “Scheduler 是 React 内部的一个独立任务调度库。它的核心作用是管理任务的执行时机。它并不关心任务具体是在做什么(比如是渲染组件还是发请求),它只负责两件事:给任务排优先级,以及判断主线程是否有空。”

English: "Scheduler is an independent task-scheduling library inside React. Its main job is to manage when tasks are executed. It doesn't care what the task actually is—whether it's rendering a component or fetching data; it only focuses on two things: prioritizing tasks and checking if the main thread is free."

  1. 它和 Concurrent Rendering 的关系 (The Relationship)

你可以用一个非常形象的逻辑来描述它们的关系:

English: "They have a 'brain and executor' relationship. Concurrent Rendering provides the architectural foundation for interruptible work, while the Scheduler leverages that by using Time Slicing. It breaks down long tasks into tiny chunks (around 5ms). After each chunk, the Scheduler checks if there’s a higher-priority task 'cutting in line,' which is what enables the smooth experience in Concurrent Mode."

  1. Scheduler 的核心机制:优先级 (Priority Levels)

中文: “Scheduler 维护了一个小顶堆队列,根据任务的过期时间来排序。它定义了几种不同的优先级,比如 Immediate(立即执行)、UserBlocking(用户阻塞级,如点击)、Normal(普通级,如请求数据)等。过期时间越短,优先级越高,越早被执行。”

English: "The Scheduler maintains a min-heap queue sorted by the expiration time of tasks. It defines several priority levels, such as Immediate, UserBlocking (e.g., clicks), and Normal (e.g., data fetching). The sooner a task expires, the higher its priority, and the earlier it gets processed."

 

 

Question 13(高频深水区 react lane)

React 的 Lane 模型是什么?它和 Scheduler 有什么区别?


1️⃣ Lane 模型是什么?

Lane Model 是 React 18 中用于表示更新优先级的一种机制

👉 它本质是:用“位掩码(bitmask)”来表示不同优先级的更新通道

你可以理解成:

每个 update 会被分配到一个或多个 lane。

 

2️⃣ Lane Model 解决什么问题?

在旧版本 React 中:

Lane Model 解决:精确区分不同更新优先级 + 支持并发合并

 

3️⃣ Lane 和 Scheduler 的区别

这是面试关键点:

概念作用
Scheduler决定“什么时候执行任务”
Lane决定“任务优先级属于哪一类”

👉 简单理解:

 

4️⃣ React 执行链路(重点)

 

5️⃣ 为什么用 Lane 而不是“数字优先级”?

因为 Lane:

👉 比“1、2、3 优先级”更灵活


简单口语版:

Lane 模型可以理解成 React 用来表示“不同优先级任务”的一种方式。

它把不同类型的更新分配到不同的“车道”里面,每个 lane 代表一种优先级。

这样 React 在处理更新的时候,可以更清楚地知道哪些是高优先级,比如用户输入,哪些是低优先级,比如后台数据更新。

而 Scheduler 的作用是决定这些任务什么时候执行,Lane 是负责标记优先级,两者是配合关系。

 

The Lane model in React is a way to represent update priorities using bitmasks.

You can think of it as different "lanes" for different priorities, where each update is assigned to one or more lanes.

This allows React to handle multiple updates with different priorities more efficiently.

The Scheduler decides when tasks should run, while the Lane model defines the priority of those tasks. They work together to enable concurrent rendering.


  1. 什么是 Lane 模型? (What is the Lane Model?)

中文: “Lane 模型是 React 18 引入的一套基于 31 位二进制位掩码(Bitmask) 的优先级机制。它把更新任务分配到不同的‘车道’上。比如,用户输入的更新占一条车道,自动保存的更新占另一条。通过二进制运算(比如位与 &),React 可以极其快速地判断哪些任务可以合并,哪些任务需要被跳过。”

English: "The Lane model is a priority system introduced in React 18 based on 31-bit binary bitmasks. It assigns updates to different 'lanes.' For instance, a user input update might occupy one lane, while an auto-save sync occupies another. By using bitwise operators (like &), React can blazingly fast determine which updates can be merged or which ones should be skipped."

  1. 它和 Scheduler 的区别? (Difference from Scheduler?)

你可以把这看作是“局部最优”到“全局把控”的升级:

中文: “最大的区别在于,Scheduler 的优先级是单线连续的,很难表达‘一批任务’的概念。而 Lane 模型可以同时选中多个‘车道’。 比如,我在处理一个搜索任务(Lane A),此时用户又点了个按钮(Lane B)。在旧模型下,我可能得放弃 A 全力做 B;但在 Lane 模型下,React 可以清晰地知道 A 和 B 是独立的,它可以根据情况决定是合并它们,还是先挂起 A 专注 B,且后续能精准地找回 A 继续做。”

English: "The main difference is that Scheduler's priorities are linear and continuous, making it hard to represent a 'group' of tasks. The Lane model, however, can select multiple lanes simultaneously. For example, if React is handling a search task (Lane A) and a user clicks a button (Lane B), the old model might force React to discard A for B. With Lanes, React can see that A and B are distinct categories. It can decide whether to batch them or suspend A to focus on B, and then accurately resume A later without losing track."

  1. 为什么 React 要从优先级(Levels)切换到 Lane?

中文: “为了更细粒度的控制。以前的优先级很难处理‘交叉’的情况。Lane 模型就像是给任务贴了标签,React 可以轻松实现:

  1. 优先级重叠:同时处理好几类不冲突的任务。
  2. 任务挂起与恢复:高优任务插队后,低优任务能被精确地‘捞回来’继续跑。”

English: "For finer-grained control. The old priority levels struggled with overlapping scenarios. The Lane model tags tasks, allowing React to:

  1. Overlap priorities: Handle multiple non-conflicting types of work at once.
  2. Suspend and Resume: After a high-priority task cuts in, React can precisely 'pluck' the low-priority task back and continue where it left off."

 

形象的总结:

Scheduler 像是单行道,车子只能按顺序排队,顶多让警车(高优)超个车。 Lane 模型 则是多车道高速公路。救护车有专门的急救车道,私家车有普通车道。React 作为指挥官,可以决定现在是只开放快车道,还是让快慢车道一起通行。这种设计让 Concurrent Mode 的调度变得极其灵活。”

 

 

Question 14(终极难点 react18整个更新流程)

React 18 的整个更新流程(从 setState 到 commit)完整是怎么执行的?


React 18 的更新流程可以拆成一条完整链路:

setState → 生成 Update → Lane 标记优先级 → Scheduler 调度 → Render 阶段 → Reconcile(调和,使协调一致,也就是diff阶段) → Commit 阶段

 

1️⃣ setState 阶段

当你调用:

React 会:

 

2️⃣ Lane 标记优先级

👉 每个 update 会被分配一个 Lane:

👉 作用:

标记这个更新“有多急”

 

3️⃣ Scheduler 调度阶段

Scheduler 会决定:

👉 “现在要不要执行这个更新?”

如果有更高优先级任务:

 

4️⃣ Render 阶段(可中断)

👉 React 开始执行:

⚠️ 特点:

 

5️⃣ Reconcile(协调)

👉 React 对比新旧 Fiber tree:

 

6️⃣ Commit 阶段(不可中断)

👉 真正执行 DOM 更新:

 

7️⃣ useEffect(最后执行)

👉 在 commit + paint 之后异步执行


一条完整链路(面试必背)


🇨🇳 中文口语版

React 的更新流程可以理解成一条流水线。

当我们调用 setState 之后,React 会先创建一个更新任务,并给它分配一个优先级,也就是 Lane。

然后 Scheduler 会决定这个任务什么时候执行,如果当前有更重要的任务,比如用户输入,它可能会先处理那个。

接下来 React 进入 render 阶段,会重新执行组件函数,生成新的虚拟 DOM 并进行 diff,这一步是可以中断的。

最后进入 commit 阶段,把变化真正应用到 DOM 上,页面才会更新。

 

🇺🇸 English spoken version

The React update process is basically a pipeline.

When setState is called, React creates an update and assigns it a priority using the Lane model.

Then the Scheduler decides when to execute it, potentially delaying low-priority updates if there are more important ones like user input.

Next comes the render phase, where React re-renders components and performs reconciliation. This phase can be interrupted.

Finally, in the commit phase, React applies changes to the DOM, and the UI is updated.

 

Question 15(高阶核心 React Suspense)

React Suspense 是做什么的?它解决了什么问题?


1️⃣ Suspense 是什么?

Suspense 是 React 提供的一种机制:

用于“等待异步数据时,优雅地暂停组件渲染,并展示 fallback UI”

 

2️⃣ 它解决什么问题?

在 Suspense 之前:

如果数据没回来:

👉 Suspense 的目标是:

让 React 在“数据未准备好时暂停渲染”,而不是渲染错误或空状态

 

3️⃣ Suspense 的核心机制

当组件遇到“未完成的数据”(Promise)时:

React 会:

  1. 暂停当前组件渲染
  2. 找到最近的 <Suspense fallback>
  3. 渲染 fallback UI
  4. 数据完成后重新渲染真实 UI

 

4️⃣ 本质(面试关键)

👉 Suspense 本质是:

“Render 阶段的中断机制 + fallback UI 控制”

 

5️⃣ 在 Next.js 中的意义

在 App Router:


简单来说,React Suspense 是一个内置组件,它允许你以声明式的方式等待异步内容的加载。它就像是给组件树里的异步任务(如数据请求或代码分割)设置了一个“中转站”。

Simply put, React Suspense is a built-in component that enables you to wait for the loading of asynchronous content in a declarative way.It acts like a "way station" for asynchronous tasks, such as data requests or code splitting, within the component tree.

面试口语化回答

  1. 它解决了什么问题? (What problem does it solve?)

中文: “它主要解决了两个痛点:第一是代码冗余。以前我们为了加载状态,要在每个组件里手动写 if (isLoading) return <Spinner />,非常啰嗦。第二是瀑布流加载(Waterfall)。多个嵌套组件发请求时,会导致界面一个接一个地闪烁。Suspense 允许我们将这些异步组件打包,整块整块地显示,让 UI 变化更自然。”

English: "It mainly solves two pain points. First, boilerplate code: previously, we had to manually handle loading states in every component using if (isLoading). Second, loading waterfalls: when nested components fetch data separately, the UI flickers as elements pop in one by one. Suspense lets us group these async components so the UI reveals itself in a more cohesive way."

  1. 它的底层“黑科技”是什么? (How does it work?)

中文: “它的原理非常神奇,叫做 ‘投掷 Promise’ (Throwing Promises)。当一个组件还在加载数据时,它会向上抛出一个错误,这个错误不是普通的异常,而是一个 Promise 对象。Suspense 捕获到这个 Promise 后,会暂时‘挂起’这个组件,先显示 fallback 里的内容。等 Promise 完成了,React 会自动回来继续渲染该组件。”

English: "The magic under the hood is called 'Throwing Promises'. When a component is still fetching data, it 'throws' a Promise instead of a regular error. Suspense catches this Promise, 'suspends' the component tree, and displays the fallback UI. Once the Promise resolves, React automatically resumes and finishes rendering that component."

 

形象的比喻:

“这就像在餐厅等餐。以前(不用 Suspense)是厨师每做完一道小菜就端上来,桌子一会儿多一个碗,很乱。现在有了 Suspense,它就像一个传菜员。你告诉他‘我要整套套餐’,他会先给你一个餐牌(fallback),等到主菜、配菜全做好了,他才一次性撤掉餐牌,把热腾腾的完整套餐端到你面前。”

 

Question 16(核心进阶 Suspense原理)

为什么 Suspense 可以“暂停渲染”?React 是怎么实现这个能力的?


Suspense 之所以可以暂停渲染,是因为:

React 在 render 阶段允许“抛出 Promise”,并通过捕获机制中断当前渲染流程

 

1️⃣ 核心机制:throw Promise

当组件在 render 中遇到未完成的数据时:

👉 React Query / Suspense-aware data source 会:

React 内部会:

 

2️⃣ Suspense boundary 的作用

👉 作用:

如果子组件“暂停”,就渲染 fallback

 

3️⃣ React 如何恢复?

当 Promise resolve:

👉 React 会:

  1. 标记这个更新完成
  2. 重新进入 render phase
  3. 继续执行之前暂停的 Fiber

 

4️⃣ 本质机制(面试核心)

Suspense 的底层不是“if loading”,而是:

基于 render 阶段的异常控制流(throw/catch)机制

 

5️⃣ 为什么要用 throw Promise?

因为 React render 是:

👉 纯函数执行阶段

无法用普通 return 表达“暂停”

所以 React 用:

 

6️⃣ 和 Concurrent Rendering 的关系

Suspense 依赖:

👉 本质是:

Concurrent Rendering + 异步数据中断机制


  1. 核心机制:投掷 Promise (The "Throw" Mechanism)

中文: “React 能够‘暂停’,本质上是利用了 JavaScript 的 throw 关键字。通常我们 throw 的是一个 Error,但 React 允许组件在数据没准备好时 throw 一个 Promise。当这个 Promise 被抛出后,React 会立刻中断当前组件的渲染,并沿着组件树向上寻找最近的 Suspense 边界。”

English: "React's ability to 'suspend' is actually powered by the throw keyword. While we normally throw an Error, React allows a component to throw a Promise if its data isn't ready. When this happens, React interrupts the rendering of that component immediately and searches up the tree for the nearest Suspense boundary."

  1. 底层状态机:捕获与回退 (Catch and Fallback)

中文:Suspense 就像是一个特殊的 try-catch 块。它捕获到那个 Promise 后,会将该子树标记为‘挂起(Suspended)’状态。此时,React 不会把半成品渲染到屏幕上,而是转而去渲染 Suspense 提供的 fallback(比如加载动画)。同时,React 会在这个 Promise 上绑定一个回调函数。”

English: "A Suspense component acts like a specialized try-catch block. Once it catches the Promise, it marks that sub-tree as 'Suspended.' Instead of committing a half-baked UI, React renders the fallback content. Meanwhile, React attaches a listener to that Promise."

  1. 恢复渲染:重试机制 (Resuming: The Retry Logic)

中文: “最关键的一点是:React 并不是真的从暂停的地方‘继续’跑,而是‘重头再来’。 当 Promise 完成(Resolved)后,它会通知 React 重新尝试渲染刚才挂起的那个组件。由于此时数据已经缓存好了,再次执行时不再抛出 Promise,组件就能顺利完成渲染。这就是为什么 Suspense 必须配合有‘缓存能力’的数据层(如 Relay 或 SWR)使用的原因。”

English: "Crucially, React doesn't 'resume' from where it left off; it 'retries' from the beginning. When the Promise resolves, it triggers React to attempt rendering that component again. Since the data is now cached, the component won't throw a Promise this time, and the render completes successfully. This is why Suspense must be paired with a data layer that has caching capabilities."

 

Execution Flow,不用说出来,了解即可

  1. Render Phase: 组件执行,发现没数据,throw promise
  2. Interruption: React 捕获 Promise,中断当前渲染。
  3. Fallback: React 同步切换到 fallback 界面。
  4. Promise Resolved: 数据到手,触发一次新的调度任务。
  5. Re-render: React 重新运行该组件,这次从缓存拿数据,渲染成功。

 

 

 

Nextjs

Question 1(基础但必问 SSR / SSG / CSR)

Next.js 中 SSR、CSR、SSG、ISR 分别是什么?有什么区别?


Next.js 中的 SSR、CSR、SSG 分别代表三种不同的渲染方式

 

1️⃣ CSR(Client-Side Rendering)

👉 客户端渲染

特点:

 

2️⃣ SSR(Server-Side Rendering)

👉 服务端渲染

特点:

 

3️⃣ SSG(Static Site Generation)

👉 静态生成

特点:

 

核心区别(面试关键)

类型发生时间是否每次请求计算性能
CSR浏览器运行时首屏慢
SSR每次请求时中等
SSG构建时最快

 

一句话总结(非常重要)

CSR 在浏览器渲染,SSR 每次请求在服务器渲染,SSG 在构建时生成静态页面。


口语化回答:

  1. SSR (Server-Side Rendering) - 服务端渲染

中文: “SSR 就是‘即点即做’。每当用户发起请求,服务器都会实时运行代码,生成一个完整的 HTML 页面发给浏览器。

English: "SSR is 'On-demand Cooking.' Every time a user makes a request, the server runs the code in real-time to generate a full HTML page.

  1. SSG (Static Site Generation) - 静态网站生成

中文: “SSG 就是‘预制菜’。在打包项目(build time)的时候,Next.js 就已经把所有页面都生成好了。用户访问时,服务器直接把现成的文件丢过去。

English: "SSG is like 'Pre-packaged Meals.' The pages are generated at build time. When a user visits, the server simply serves the pre-built static files.

  1. CSR (Client-Side Rendering) - 客户端渲染

中文: “CSR 就是‘自助餐’。服务器先给一个几乎空白的 HTML 壳子,然后浏览器下载 JS,在用户的电脑上跑代码,再去请求数据并渲染。

English: "CSR is like a 'Self-service Buffet.' The server sends a bare-bones HTML shell. The browser then downloads the JavaScript, runs it on the user's device, fetches data, and renders the UI.

核心区别对比 (The Key Differences)

方案什么时候生成 HTML?性能 (Performance)数据实时性 (Freshness)
SSR用户请求时 (Request Time)中 (depends on server)🚀 极高
SSG构建打包时 (Build Time)🚀 极快📉 较低
CSR浏览器运行时 (Runtime)取决于用户设备🚀 极高

 

Question 2 (讲解一下ISR)

中文: “ISR (Incremental Static Regeneration),中文叫‘增量静态再生’。它是 Next.js 提供的一种在应用运行期间,无需重新构建整个项目,就能更新已有的静态页面或创建新页面的机制。它的核心价值是让静态内容(SSG)具备了动态更新的能力。”

English: "ISR (Incremental Static Regeneration) is a mechanism in Next.js that allows you to update existing static pages or create new ones after you’ve built your site, without needing a full re-build. Its core value is bringing dynamic updates to static content (SSG)."

 

  1. 核心定义:SSG 与 SSR 的完美平衡 (The Best of Both Worlds)

中文: “简单来说,ISR 让我们既能享受 SSG 的极致速度(放在 CDN 上的静态文件),又能拥有 SSR 的数据实时性。它允许你在不重新打包整个网站的情况下,在后台自动更新特定的静态页面。”

English: "In short, ISR gives us the performance of SSG (static files served from a CDN) with the flexibility of SSR. It allows you to update specific static pages in the background without needing a full re-build of the entire site."

  1. 工作原理:Stale-While-Revalidate (SWR 模式)

中文: “它的底层逻辑是 ‘过期即更新’。比如你设置 revalidate: 60(60秒):

English: "It works on the Stale-While-Revalidate pattern. Let’s say you set revalidate: 60:

  1. 为什么面试官喜欢问 ISR?(解决了什么痛点)

中文: “它解决了 ‘打包时间过长’ 的问题。如果你有 10 万个商品页面,用 SSG 每次改个错字都要打包几小时,这根本没法维护。用 ISR,你可以只预渲染最热门的 100 个页面,其他的等用户访问时再动态生成并缓存,这大大提升了大规模应用的构建效率。”

English: "It solves the 'long build times' problem. If you have 100,000 product pages, a full SSG build could take hours for just a tiny typo fix. With ISR, you can pre-render the top 100 popular pages and let the rest be generated and cached on-demand, which drastically improves build efficiency for large-scale apps."

  1. 进阶:按需更新 (On-demand Revalidation)

中文: “除了定时更新,ISR 现在还支持 ‘按需更新’。比如你在 CMS 里改了一篇文章,可以通过一个 Webhook 触发 revalidateTag,让 Next.js 立刻失效旧缓存并更新。这样数据更新就从‘等一分钟’变成了‘秒级同步’。”

English: "Beyond time-based updates, ISR now supports On-demand Revalidation. If you update an article in your CMS, you can trigger a Webhook to call revalidateTag. This tells Next.js to invalidate the cache and update the page immediately, bringing data sync down from minutes to seconds."

 

方案优点缺点
SSG极快,CDN 友好数据容易过时,大项目打包慢
SSR数据最实时,个性化强服务器压力大,首字节时间(TTFB)长
ISR兼顾性能与实时性第一个“过期”访问者仍会看到旧数据

 

 

Question 3(Next.js 核心原理 SSR / SSG的区别)

SSR 和 SSG 在 Next.js 中的本质区别是什么?(底层执行机制)

👉 提示你思考:


SSR 和 SSG 的本质区别不是“都是在服务端渲染”,而是:

👉 HTML 是在什么时候生成的,以及生成结果能不能复用

 

1️⃣ SSR(Server-Side Rendering)

👉 每次请求都生成 HTML

执行过程:

  1. 用户访问页面
  2. Next.js 在服务器执行 React 组件
  3. 生成 HTML
  4. 返回给浏览器

⚠️ 特点:

 

2️⃣ SSG(Static Site Generation)

👉 在 build 时生成 HTML

执行过程:

  1. 项目 build
  2. Next.js 提前把页面渲染成 HTML
  3. 存成静态文件
  4. 用户请求直接返回文件

⚠️ 特点:

 

核心本质区别(面试关键)

维度SSRSSG
生成时间请求时构建时
是否每次计算
是否可缓存一般不可以(CDN)
性能最快
数据新鲜度实时非实时

 

一句话总结(必须背)

SSR 是“每次请求生成页面”,SSG 是“提前构建好页面直接复用”。


  1. 核心执行时机 (Execution Timing)

中文: “本质区别在于 HTML 生成的时间点。SSG 是构建时(Build-time)生成的,你跑打包命令时页面就定型了;而 SSR 是请求时(Request-time)生成的,只有用户点进来的那一刻,服务器才开始忙活。”

English: "The fundamental difference lies in when the HTML is generated. SSG happens at Build-time, meaning the pages are pre-rendered when you run the build command. SSR, on the other hand, happens at Request-time—the server only starts working the moment a user makes a request."

  1. 服务器压力与缓存 (Server Load & Caching)

中文: “在底层,SSG 非常快,因为它是静态文件,可以直接存在 CDN 上,不需要服务器计算。而 SSR 每次都要跑一次函数(如 getServerSideProps),服务器压力大,响应时间(TTFB)也会因为数据请求的快慢而波动。”

English: "Under the hood, SSG is incredibly fast because the files are static and can be cached on a CDN, bypassing the server entirely. SSR requires the server to execute code (like getServerSideProps) for every request, which increases server load and makes the TTFB dependent on how fast your data APIs respond."

  1. 数据灵活性 (Data Flexibility)

中文: “SSR 的优势在于灵活性。因为它在运行时执行,所以它能拿到请求里的 Cookies 或用户信息,做个性化展示。SSG 就比较死板,它不知道当前访问的人是谁,只能展示大家都一样的通用内容。”

English: "The strength of SSR is its flexibility. Since it runs at runtime, it has access to request-specific data like Cookies or user headers, allowing for personalized content. SSG is more rigid; it doesn't know who the user is and can only serve generic content that's the same for everyone."

 

 

 

Question 4(Next.js 高频核心 hydration)

什么是 hydration?为什么 SSR 之后还需要 hydration?


1️⃣ Hydration 是什么?

👉 Hydration(“注水”)指的是:

React 在 SSR 生成的静态 HTML 上“接管”并绑定事件的过程

关键点:

SSR 返回的 HTML:

 

👉 Hydration 做的事:

 

2️⃣ 为什么 SSR 之后必须 hydration?

因为 SSR 只是:把 HTML 画出来

但 React 应用需要:

👉 所以必须再跑一遍 React:

 

3️⃣ 没有 hydration 会怎样?

页面会变成:

👉 就像“只有壳子的网页”

 

4️⃣ Hydration 本质一句话

复用 SSR 的 DOM,而不是重新创建 DOM

 

面试加分点(很多人不知道)

Hydration 并不是“重新渲染页面”,而是:

对已有 DOM 做事件绑定 + diff 校验

如果 SSR 和客户端不一致,会出现:

👉 hydration mismatch(报错)


  1. 什么是 Hydration? (What is Hydration?)

中文: “Hydration 简单来说就是 ‘激活’ 静态 HTML 的过程。SSR 之后,服务器传给浏览器的只是一个没有任何交互能力的 HTML 字符串(像一张照片)。浏览器下载完 JS 脚本后,React 会重新运行一遍代码,把事件监听器(比如 onClick)挂载到这些现有的 DOM 元素上,让页面变得可交互。”

English: "Hydration is essentially the process of 'activating' static HTML. After SSR, the server sends a plain HTML string to the browser that has no interactivity (like a photograph). Once the browser downloads the JavaScript, React runs through the code again to attach event listeners (like onClick) to the existing DOM elements, making the page 'alive' and interactive."

  1. 为什么 SSR 之后还需要它? (Why is it necessary after SSR?)

核心矛盾:HTML 只有结构,JS 才有逻辑。 (HTML has structure; JS has logic.)

中文: “因为 HTML 本身只能展示文字和图片,它并不理解 React 的状态(State)或副作用(Effects)。如果没有 Hydration,你点击按钮页面是不会有任何反应的。React 需要通过 Hydration 在浏览器端重建一份虚拟 DOM(Virtual DOM),并将其与服务器传来的真实 DOM 进行比对和绑定,从而接管后续的页面更新。”

English: "Because HTML can only display text and images; it doesn't understand React concepts like State or Effects. Without hydration, clicking a button would do absolutely nothing. React needs hydration to reconstruct the Virtual DOM on the client-side and 'match' it with the real DOM sent by the server, allowing React to take over and handle future updates."

  1. Hydration 过程中最常见的问题 (Common Issues)

中文: “面试中常提到的 ‘Hydration Mismatch’ (水合不匹配)。如果你的代码里用了 window 对象或者 Date.now(),导致服务器生成的 HTML 和客户端计算出的结果不一致,React 就会报警告,甚至导致页面渲染闪烁。这是因为 React 要求两边的初始渲染结果必须完全对齐。”

English: "A common interview topic is the 'Hydration Mismatch.' If your code uses things like the window object or Date.now(), the HTML generated on the server might differ from what the client calculates. This triggers a React warning or UI flickering because React expects the initial render result to be an exact match on both sides."

 

形象的比喻 (The Analogy)

“SSR 就像是送外卖。服务器把做好的饭菜(HTML)送到你家,你一眼就能看到是什么菜,但你还没法吃。Hydration 就像是递给你一双筷子(JS 交互),只有拿到了筷子,你才能真正开始吃饭(进行交互)。”

 

 

Question 5(高频 + 深水区 hydration和client render的区别)

Hydration 和重新渲染(client render)有什么区别?为什么不能直接重新渲染?


1️⃣ 两者本质区别

✔️ Hydration(注水)

👉 复用 SSR 已经生成的 HTML,只“接管它”

做的事情:

 

❌ Client Re-render(重新渲染)

👉 完全重新生成 DOM

做的事情:

 

2️⃣ 为什么不能直接重新渲染?

因为 SSR 的核心目标是:

🚀 “让用户尽快看到页面”

如果直接 client re-render:

❌ 会发生:

  1. 服务器返回 HTML(SSR)
  2. 浏览器刚显示
  3. React 又删掉 DOM
  4. 再重新画一遍

👉 结果:

 

3️⃣ Hydration 的意义

👉 Hydration 的设计目标是:

尽可能复用 SSR 的结果,而不是推倒重来

 

4️⃣ 一个简单类比(面试很好用)

👉 SSR + Hydration:

服务器先帮你“装修好房子”,前端只是“开灯 + 接电”

👉 Client re-render:

先给你一个毛坯房,然后你自己重新装修一遍

 

5️⃣ 核心一句话(面试必背)

Hydration 是复用 SSR 生成的 DOM 并进行事件绑定,而重新渲染是完全重建 DOM,两者成本和目的完全不同。


简单来说:重新渲染(Client Render)是“推倒重来”,而 Hydration 是“原地复活”。

  1. 核心区别 (Core Difference)

Hydration (水合)

Client Render (重新渲染)

  1. 为什么不能直接重新渲染? (Why not just re-render?)

原因 A:用户体验(闪烁问题) / User Experience (Flicker)

原因 B:性能开销 / Performance Overhead

原因 C:保持组件状态 / Preserving Local State

 

Question 6(Next.js 核心难点 server / client components 的区别)

Next.js App Router 中 Server Components 和 Client Components 有什么区别?


在 Next.js App Router 中,组件默认是 Server Component(服务端组件),除非你显式写:

 

1️⃣ Server Components(默认)

👉 在 服务器执行

特点:

👉 本质:只负责“生成 HTML”,不参与交互

 

2️⃣ Client Components("use client")

👉 在 浏览器执行

特点:

👉 本质:负责“交互逻辑”

 

3️⃣ 核心区别(面试重点)

维度Server ComponentClient Component
执行环境ServerBrowser
是否有 JS bundle❌ 没有✅ 有
是否能用 hooks❌ 不可以✅ 可以
是否能访问 DB✅ 可以❌ 不可以
作用生成 HTML交互逻辑

 

4️⃣ 为什么 Next.js 要这样设计?

👉 核心目标:

减少客户端 JS,提高性能

如果全部都是 Client Component:

如果用 Server Component:

 

5️⃣ 一个非常重要的理解(面试加分)

👉 Next.js App Router 的理念是:

Server Components 负责“结构 + 数据”,Client Components 负责“交互”

 

6️⃣ 一句话总结(必须背)

Server Components 在服务器执行,不包含交互逻辑;Client Components 在浏览器执行,负责交互和状态管理。


  1. 运行环境与生命周期 (Runtime & Lifecycle)

中文: “两者的本质区别在于运行环境。Server Component 只在服务器端执行并渲染成一种特定的 JSON 格式(RSC Payload),其源代码永远不会流向浏览器。而 Client Component 虽然也会在服务端预渲染(SSR),但它最终必须在客户端下载 JS 并完成 Hydration(水合) 才能生效。”

English: "The fundamental difference is the runtime environment. Server Components execute exclusively on the server and are rendered into a specific format called RSC Payload—their source code never reaches the browser. Client Components, while pre-rendered on the server (SSR), ultimately require downloading JavaScript and undergoing Hydration in the browser to become functional."

  1. 数据获取与包体积优化 (Data Fetching & Bundle Size)

中文: “从性能角度看,Server Component 解决了 ‘包体积膨胀’ 的问题。因为它们在后端运行,我们可以直接在组件内调用数据库或文件系统,而无需暴露 API 端点,且其依赖的第三方库不会增加前端包体积。这实现了一种真正的 Zero-Bundle-Size 组件。”

English: "From a performance perspective, Server Components solve the 'bundle bloat' issue. Since they run on the backend, we can access databases or file systems directly without exposing API endpoints, and any dependencies used stay on the server. This enables true Zero-Bundle-Size components."

  1. 交互边界与约束 (Interactivity Boundaries & Constraints)

中文: “在架构设计上,两者的界限非常清晰。Server Component 负责数据密集型逻辑,但它不支持任何状态处理(Hooks)或 DOM 事件。Client Component 则是交互中心,支持所有 React 特性。在配合上,我们利用 Server Component 作为容器来获取数据,通过 Props 或 Children 将数据注入到 Client Component 中,从而兼顾首屏速度与交互体验。”

English: "Architecturally, the boundaries are well-defined. Server Components handle data-heavy logic but don't support state (Hooks) or DOM events. Client Components serve as the interactivity hub, supporting all React features. In practice, we use Server Components as containers to fetch data and inject it into Client Components via Props or Children, balancing initial load speed with a rich user experience."

 

面试官眼中的“高分关键词”:

 

 

Question 7(非常高频 server components 能完全取代 client components吗?)

Server Components 既然在服务器执行,那为什么不能直接完全替代 Client Components?


Server Components 不能完全替代 Client Components,原因不只是“不能用浏览器 API”,而是因为:

👉 服务端和浏览器承担的是两类完全不同的职责:渲染 vs 交互

 

1️⃣ Server Components 的限制

你说的点是对的:

👉 因为它运行在:

Node.js server 环境

 

2️⃣ 更关键的原因(面试重点)

Server Components 不能处理“持续交互”

比如:

这些都需要:

👉 浏览器中的 runtime(React DOM)

 

3️⃣ React 本质是“双运行时模型”

Next.js App Router 是:

如果只有 Server Components:

👉 页面只能“生成一次”,无法更新 UI

 

4️⃣ 为什么不能只靠 server?

因为浏览器必须负责:

👉 server 做不到“持续响应 UI 变化”

 

5️⃣ 一个面试很好用的类比

👉 Server Components:

像“后端生成一份静态页面”

👉 Client Components:

像“前端让页面活起来(可以点、可以动)”

 

6️⃣ 一句话总结(必须背)

Server Components 只能负责生成静态 UI,而 Client Components 负责浏览器端的交互和状态管理,两者职责不同,不能互相替代。


  1. 响应延迟与实时交互 (Latency & Real-time Interactivity)

中文: “最根本的原因是网络延迟。Server Component 的任何逻辑运行都需要跨越网络。如果把‘按钮点击’或‘输入框打字’也交给服务端处理,用户每操作一下都要等一个网络往返(RTT),这种体验是不可接受的。Client Components 运行在本地内存中,能够提供毫秒级的即时反馈,这是服务端无法替代的。”

English: "The most fundamental reason is network latency. Any logic in a Server Component requires a network round-trip. If we handled 'button clicks' or 'input typing' on the server, users would feel a delay with every single action. Client Components run in local memory, providing millisecond-level instant feedback, which is something the server simply cannot match."

  1. 浏览器 API 与状态持久化 (Browser APIs & State Persistence)

中文: “Server Components 无法访问浏览器特有的 API,比如 windowlocalStorage、屏幕尺寸,或者是用于动画的 requestAnimationFrame。此外,由于它们不具备状态保持(Hooks)的能力,我们无法在不刷新页面的情况下维持复杂的 UI 状态。Client Components 的任务就是接管这些‘浏览器上下文’相关的逻辑。”

English: "Server Components lack access to browser-specific APIs like window, localStorage, or animation-related APIs like requestAnimationFrame. Moreover, because they don't support Hooks for state persistence, we can't maintain complex UI states without a full re-render. Client Components are essential for managing logic tied to the browser context."

  1. 用户体验的连续性 (User Experience Continuity)

中文: “如果完全依赖服务端,我们就失去了客户端侧的路由平滑过渡局部状态保留。Client Components 允许我们在数据加载时保持页面的交互性(如骨架屏、滚动位置记录)。如果全部替代,应用就会退化成传统的 MPAs(多页面应用),每次交互都可能面临整个组件树的重置。”

English: "If we relied solely on the server, we would lose smooth client-side transitions and local state preservation. Client Components allow the UI to remain interactive while data is loading—think skeleton screens or maintaining scroll positions. Without them, the app would revert to traditional MPAs, where every interaction could potentially reset the entire component tree."

 

 

Question 8(Next.js 核心难点 缓存机制)

Next.js 中的缓存机制(cache / revalidate)是怎么工作的?


Next.js 的缓存机制本质是:

👉 对“请求结果(HTML / 数据 / fetch 结果)进行分层缓存 + 可控失效(revalidate)”


1️⃣ Next.js 有哪几层缓存?

在 App Router 中,主要有三层:

① Data Cache(数据缓存)

👉 fetch 的结果可以被缓存

特点:

② Full Route Cache(路由缓存)

👉 整个页面的 HTML 会被缓存

③ Request Memoization(请求去重)

👉 同一个 render 过程中的重复请求会被合并

 

2️⃣ revalidate 是什么?

👉 控制“缓存多久过期”

① ISR(Incremental Static Regeneration)

👉 意思是:

每 60 秒可以重新生成一次页面

② 工作流程:

 

3️⃣ SSR / SSG / ISR 在缓存上的本质区别

模式是否缓存何时更新
SSR❌ 默认不缓存每次请求
SSG✅ 永久缓存build 时
ISR✅ 有缓存 + 定时更新revalidate

 

4️⃣ Next.js 为什么要做缓存?

👉 核心目的:

 

5️⃣ 一个非常重要的理解(面试加分)

👉 Next.js App Router 的本质:

不是“每次都渲染 React”,而是“尽可能复用缓存结果”

 

6️⃣ 一句话总结(必须背)

Next.js 通过 Data Cache、Route Cache 和 Request Memoization 实现多层缓存,并通过 revalidate 控制缓存失效,实现性能优化与数据更新的平衡。


  1. 缓存的四个层级 (The Four Levels of Caching)

中文: “Next.js 的缓存不是单一的,它分为四个层级:请求记忆 (Request Memoization)数据缓存 (Data Cache)全路由缓存 (Full Route Cache)客户端路由器缓存 (Router Cache)。它们分别在渲染的不同阶段、不同的位置(内存、磁盘或浏览器)起作用。”

English: "Next.js caching isn't just one thing; it operates across four levels: Request Memoization, Data Cache, Full Route Cache, and Router Cache. They function at different stages of rendering and in different locations, like memory, disk, or the browser."

  1. cache()fetch() 的工作原理 (How cache and fetch work)

中文: “最底层的是 Request Memoization。如果你在同一个渲染周期里、不同的组件中调用了同一个 URL 的 fetch,Next.js 会自动帮你去重,确保只发一次请求。这是在服务器内存中完成的,渲染完就销毁。而 Data Cache 则是持久化的,它会跨请求、跨用户地把数据存在服务器磁盘上,这也就是我们常说的 force-cache。”

English: "At the most basic level is Request Memoization. If you call the same fetch URL in different components during a single render pass, Next.js automatically deduplicates it. This happens in server memory and is cleared after the render. The Data Cache, however, is persistent; it stores data on the server disk across different requests and users, which is what we call force-cache."

  1. revalidate 的触发机制 (Revalidation Mechanism)

中文:revalidate 解决了静态数据‘过期’的问题。它主要有两种方式:

English: "revalidate solves the problem of stale data. It primarily works in two ways:

  1. 路由缓存的差异 (Route Cache vs. Router Cache)

中文: “这里容易混淆。Full Route Cache 是在服务器端把整个页面的渲染结果(RSC Payload 和 HTML)存起来。而 Router Cache 是在浏览器端缓存已经访问过的路由片段。这解释了为什么你在 Next.js 应用中点击‘前进/后退’时速度极快,因为它根本没走后端,直接从浏览器内存里取了数据。”

English: "It's easy to get these confused. Full Route Cache stores the rendered result (RSC Payload and HTML) on the server. Router Cache, meanwhile, caches visited route segments in the browser's memory. This explains why navigating 'back and forth' in a Next.js app is so fast—it’s pulling data directly from browser memory without hitting the backend."

 

 

Question 9(终极高频 app router解析)了解即可

Next.js 为什么 App Router 要基于 React Server Components?解决了什么问题?


Next.js App Router 选择基于 React Server Components(RSC),核心是为了解决:

传统 React 应用“客户端 JS 过重 + SSR hydration 成本高 + 数据获取混乱”的问题

1️⃣ 旧问题(Pages Router / 传统 SSR)

在 App Router 之前(Pages Router):

❌ 问题1:JS 太多

即使是 SSR:

👉 结果:首屏快,但 JS 依然重

❌ 问题2:Hydration 成本高

👉 页面大时很慢

❌ 问题3:数据获取分散

👉 数据逻辑不统一

 

2️⃣ RSC 带来的核心变化

React Server Components 的核心思想:

👉 把“渲染逻辑”尽量放到 server,减少 client JS

 

3️⃣ App Router 的解决方案

✔️ 1. 减少 Client JS

Server Components:

👉 JS 体积大幅下降

✔️ 2. 消除部分 hydration

只有 Client Components 才需要 hydration:

👉 减少 React 在浏览器的工作量

✔️ 3. 数据获取更自然

直接在 Server Component 中:

👉 不再需要 getServerSideProps / getStaticProps

 

4️⃣ 本质总结(面试关键)

Next.js App Router + RSC 的本质是:

👉 重新划分 React 的职责边界

Server Components:

Client Components:

 

5️⃣ 一句话总结(必须背)

👉 中文:

Next.js 使用 React Server Components 是为了减少客户端 JavaScript、降低 hydration 成本,并统一服务端数据获取模型,从而提升性能和架构清晰度。


口语化回答:

  1. 彻底解决“JavaScript 膨胀”问题 (Zero Bundle Size)

中文: “以前的 SSR(Pages Router)虽然首屏快,但浏览器还是得下载所有的 JS 逻辑来完成水合。App Router 基于 RSC 后,默认所有组件都是服务端组件,它们的逻辑和第三方库(比如巨大的 Markdown 解析库)完全不会发到浏览器。这让前端包体积保持在极小的水平,特别是对那些交互不多的页面,性能提升是断层式的。”

English: "In the old SSR (Pages Router), even though the initial load was fast, the browser still had to download all the JS for hydration. Because App Router is based on RSC, all components are Server Components by default. Their logic and heavy dependencies never reach the browser. This enables Zero Bundle Size for non-interactive parts, which is a massive performance leap for content-heavy sites."

  1. 消除“数据获取”的瀑布流 (Solving Data Waterfalls)

中文: “在传统的客户端渲染里,父组件发完请求、渲染完,子组件才能开始发请求,这就形成了‘瀑布流’。现在有了 RSC,数据获取直接在服务端组件内部异步完成,离数据库更近且延迟极低。而且它支持流式传输 (Streaming),React 可以在数据还没全回来时先传个骨架屏(Skeleton),数据一到立刻‘填空’,这种分块加载的体验比以前要顺滑得多。”

English: "Traditional client-side rendering often suffers from Data Waterfalls, where child components have to wait for parents to finish fetching data. With RSC, data fetching happens directly inside Server Components on the backend, closer to the source with minimal latency. Plus, it supports Streaming with Suspense, allowing React to send UI chunks as they're ready. This makes the loading experience much smoother and more progressive."

  1. 更现代的开发范式 (Simplified Development Model)

中文: “它解决了 Pages Router 里数据获取逻辑过于集中的痛点。以前我们必须在顶层写 getServerSideProps 然后通过 Props 一层层传下去。现在你可以在任何一个服务端组件里直接写 async/await 取数据,就像写后端代码一样直观。这不仅让组件更独立、更好维护,还通过 Server Actions 统一了前后端数据交互的方式。”

English: "It fixes the centralized data fetching pain point in Pages Router. Instead of being forced to use getServerSideProps at the top level and prop-drilling everything down, you can now use async/await inside any Server Component. It feels just like writing backend code. This, combined with Server Actions, creates a much more cohesive and maintainable full-stack development experience."

 

Question 10 Cache Components是什么

  1. 核心定义 (Professional Definition)

中文:Cache Components 是 Next.js 16 引入的一种全新的编程模型。它的核心思想是‘显式缓存,默认动态’。以前 Next.js 会自动尝试缓存整个页面,导致开发者经常被‘陈旧数据’困扰;现在通过在 next.config.ts 中开启 cacheComponents 标志,你可以使用 'use cache' 指令,精确地指定哪些组件或函数需要被缓存。”

English: "Cache Components is a new programming model introduced in Next.js 16. The core philosophy is 'Explicit Caching, Dynamic by Default.' Previously, Next.js tried to cache entire pages automatically, which often led to stale data issues. Now, by enabling the cacheComponents flag in your config, you can use the 'use cache' directive to precisely mark which specific components or functions should be cached."

  1. 解决了什么痛点? (What problem does it solve?)

中文: “它彻底解决了 ‘缓存不可控’ 的问题。在旧版本中,只要你在页面深处用了一个 cookies(),整个路由就会变成动态渲染。现在有了 Cache Components,它配合 PPR (部分预渲染),允许你在一个动态页面里,把不常变动的‘侧边栏’或‘商品介绍’组件单独标记为缓存。这实现了组件级别的静态与动态共存。”

English: "It completely solves the 'Opaque Caching' problem. In older versions, using cookies() deep in the tree would force the entire route to be dynamic. With Cache Components and PPR (Partial Prerendering), you can now isolate and cache a 'Sidebar' or 'Product Description' within a dynamic page. This allows static and dynamic content to coexist seamlessly at the component level."

  1. 'use cache' 指令的用法 (How to use the directive)

中文: “你可以把 'use cache''use client' 一样放在文件顶部,或者直接放在异步函数/组件内部

English: "You can place 'use cache' at the top of a file (like 'use client') or directly inside an async function or component.

  1. 面试技术细节 (Technical Deep Dive)

 

Question 11 Cache Components 相关面试题

第一题:基础概念

问:Next.js 15/16 为什么要引入 use cache?它和传统的 fetch 缓存有什么本质区别?

中: “引入 use cache 是为了把缓存的控制权从框架自动推断转变为开发者手动声明。以前 Next.js 是‘黑盒缓存’,只要你用了 cookies(),整个路由就变动态了,很难局部缓存。 本质区别在于:fetch 缓存只能存网络请求的数据,而 use cache组件级的持久化缓存。它不仅能存数据,还能直接把组件渲染后的 RSC Payload(React 树结构) 存到服务器磁盘上。这意味着下次访问时,React 连组件代码都不用跑,直接把结果搬出来就行。”

En: "The introduction of use cache shifts the caching model from automatic inference to explicit declaration. Previously, Next.js caching was a 'black box'—using cookies() would force the entire route to be dynamic, making granular caching difficult. The key difference is that fetch caching only stores raw data, while use cache provides component-level persistent caching. It doesn't just store data; it caches the rendered RSC Payload on the server disk. This means for subsequent requests, React doesn't even need to execute the component code; it just serves the pre-rendered result."


第二题:架构设计(与 PPR 结合)

问:Cache Components 是如何配合 Partial Prerendering (PPR) 工作的?

中: “它们是‘黄金搭档’。PPR 的目标是一个页面里既有静态部分也有动态部分。 在以前,这种划分很模糊。现在有了 Cache Components,我们可以把页面中那些‘耗时但数据不常变’的组件,比如商品详情或复杂的侧边栏,打上 'use cache'。这样在 PPR 模式下,这些组件会被当作静态块提前生成并缓存,而页面其他部分保持动态。这大大提升了复杂页面的首屏响应速度。”

En: "They are the 'perfect pair.' The goal of PPR is to have both static and dynamic parts within a single page. Previously, this boundary was blurry. With Cache Components, we can mark expensive but stable components—like product descriptions or complex sidebars—with 'use cache'. In PPR mode, these are treated as static chunks that are pre-generated and cached, while the rest of the page remains dynamic. This significantly boosts the initial response time for complex routes."


第三题:底层细节(Cache Key)

问:如果我在一个组件里用了 'use cache',React 是怎么知道什么时候该给不同用户返回不同的缓存内容的?

中: “这涉及到了 Cache Key(缓存键) 的生成机制。React 编译器非常聪明,它会自动根据你传给组件的 Props(参数) 来生成唯一标识。 比如你有一个 UserCard 组件,你传入不同的 userId,React 就会自动为每个 ID 生成一份缓存。你不需要像以前写 revalidateTag 那样手动管理 key。只要参数变了,缓存就会自动隔离。这种‘自动参数绑定’极大地降低了手动管理缓存的复杂度和出错率。”

En: "This involves the Cache Key generation mechanism. The React compiler is smart enough to automatically generate a unique ID based on the Props (arguments) passed to the component. For example, if you have a UserCard component and pass different userIds, React automatically creates separate cache entries for each ID. You don't have to manually manage keys like we did with revalidateTag. As long as the inputs change, the cache is isolated. This 'automatic param-binding' significantly reduces the complexity and bugs associated with manual cache management."


第四题:实战避坑

问:在 Cache Components 内部使用 headers()cookies() 会发生什么?

中: “这是一个很好的陷阱题。如果在带有 'use cache' 的组件内部直接调用 headers(),React 会直接报错或者抛出警告。 因为 use cache 的目的是让组件变得‘可静态化’,而 headers 是典型的动态请求信息。如果你确实需要根据用户头信息来展示内容,正确的做法是在外部(父组件)获取这些信息,然后作为 Props 传进去。这样 React 就能根据传入的 Props 决定是命中缓存还是重新渲染,从而维持了组件的纯净性。”

En: "That’s a great 'trap' question. If you call headers() directly inside a component marked with 'use cache', React will throw an error or a warning. Since the goal of use cache is to make a component 'statically cacheable,' and headers are inherently dynamic request data, they are incompatible. If you need to display content based on headers, the correct pattern is to read them in a parent component and pass them in as Props. This allows React to decide whether to hit the cache based on those inputs, maintaining the component's purity."

 

Question 12 ISR / SSR / CSR / SSG 渲染方式 和 server components / client components有关系吗?是不是server components就是ssr方式渲染,而client components是CSR方式渲染?

这是一个非常经典且关键的误区。答案是:它们不是等价关系,而是“渲染策略”与“组件类型”的关系。

你可以用以下这个逻辑给面试官一个专业且清晰的回答:

  1. 核心结论 (The Core Conclusion)

中文: “不能简单地划等号。Server/Client Components 定义的是组件在哪里运行以及能访问什么资源;而 SSR/SSG/CSR 定义的是 HTML 生成的时机。 最典型的反直觉点在于:Client Components 也会参与 SSR 预渲染。

English: "You can't simply equate them. Server/Client Components define where the code runs and what resources it can access, while SSR/SSG/CSR define *when* the HTML is generated.The most counter-intuitive point is: Client Components also participate in SSR pre-rendering."

  1. 深度拆解 (Deep Dive)

Client Components ≠ 纯 CSR

中文: “很多人认为 Client Components 只在浏览器跑,这不对。在 Next.js 中,当你访问一个 SSR 页面时,页面上的 Client Components 也会在服务器上跑一遍,生成静态 HTML(这就是预渲染),这样用户才能瞬间看到内容。等到 JS 下载完,它们才在浏览器完成 Hydration(水合) 变得可交互。所以,Client Component 其实是 SSR + Hydration。”

English: "Many think Client Components only run in the browser, but that's incorrect. In Next.js, when you visit an SSR page, the Client Components are also executed on the server to generate the initial HTML (pre-rendering). This allows users to see content instantly. Once the JS loads, they undergo Hydration in the browser. So, Client Components are actually SSR + Hydration."

 

Server Components ≠ 纯 SSR

中文: “Server Components 是 React 18 引入的新物种。它们的输出不是 HTML,而是一种 RSC Payload(特殊的 JSON)。它们可以配合 SSG(在构建时跑完存起来),也可以配合 SSR(在请求时跑完传给前端)。它们最大的特点是永远不会在浏览器运行,所以它们的 JS 体积是 0。”

English: "Server Components are a new breed introduced in React 18. Their output isn't HTML, but rather an RSC Payload (a special JSON). They can work with SSG (executed at build time and stored) or SSR (executed at request time). Their defining trait is that they never run in the browser, resulting in zero client-side JS."

  1. 三者关系的本质 (The Essence of the Relationship)

你可以给面试官总结这个“包含关系”:

  1. 面试总结对照 (Quick Comparison)
维度 (Dimension)Server Components (RSC)Client Components ('use client')
是否参与 SSR/SSG?✅ 参与 (生成数据/结构)✅ 参与 (生成初始 HTML)
是否在浏览器运行?从不必然运行 (用于交互)
底层渲染方式主要是服务端生成内容预渲染 + 客户端 Hydration

一句话总结 (In short):

“Server/Client Components 决定了代码的归属地,而 SSR/SSG 决定了渲染的排班表。Client Component 并不代表放弃服务端渲染,它只是为了在服务端生成的壳子里注入交互的‘灵魂’。” "Server/Client Components define where code lives, while SSR/SSG defines the rendering schedule. Opting for a Client Component doesn't mean giving up server-side rendering; it's about injecting the 'soul' of interactivity into a server-generated shell."