React 面试准备

基础

为什么在渲染列表时应该使用 key prop

React 通过 key 对比新旧虚拟 DOM 树中的 DOM 节点项,仅更新发生变化的项,避免全量重渲染
React 的 Diff 算法默认按‌同级索引‌对比节点。若无 key,列表排序或增删时,React 误判为“旧节点销毁 + 新节点创建”,导致不必要的挂载/卸载;若有稳定 key,React 能识别节点只是位置变化,直接移动 DOM 并保留状态 。‌

useCallback 和 useMemo 的区别

useMemo 和 memo 的区别

Hooks

Hooks 的规则

Zustand

为什么需要 Zustand

  1. Prop Drilling:通过 props 传状态传递状态会导致代码机器臃肿
  2. 使用 React Context 方案时一旦 Context 的值发生了变化,所有消费了这个 Context 的组件都会被迫重新渲染

Zustand 样板代码

import { create } from 'zustand'

interface BearState {
  bears: number
  increasePopulation: () => void
  removeAllBears: () => void
}

const useBearStore = create<BearState>((set) => ({
  bears: 0, // States
  
  // Actions
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

在组件中使用它:

function BearCounter() {
  // 正确姿势:使用 Selector 精准订阅 bears
  const bears = useBearStore((state) => state.bears)
  return <h1>{bears} around here ...</h1>
}

function Controls() {
  // 获取修改状态的方法
  const increasePopulation = useBearStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}

Zustand 最佳实践

  1. 永远使用 Selector
// 错误:这会导致当前组件订阅了整个 Store 的所有变化
const state = useBearStore() 

而是使用 Selector 来精准订阅 store

const bears = useBearStore((state) => state.bears)
  1. 处理多状态订阅:使用 useShallow

如果一个组件需要同时引入 Store 里的多个状态,为了避免写好几行 Hooks,你可以写在一行,但必须加上 useShallow

import { useShallow } from 'zustand/react/shallow'

// 选出多个状态,并进行浅比较
const { bears, color } = useBearStore(
  useShallow((state) => ({ bears: state.bears, color: state.color }))
)

为什么要用 useShallow 因为每次执行 (state) => ({ bears: ..., color: ... }) 都会返回一个新的字面量对象 {}。在 JS 中,{} !== {}。如果不加 useShallow,Zustand 会认为状态每次都变了,从而引发无意义的重复渲染。

3. 将 Actions 结构化(推荐方案)

当项目变大时,Store 里的 Action 会非常多。把它们打包到一个 actions 对象中,可以让组件调用时清晰度翻倍:

const useUserStore = create((set) => ({
  username: 'Gamer',
  level: 1,
  
  // 把所有修改状态的方法聚合在一起
  actions: {
    updateName: (name) => set({ username: name }),
    levelUp: () => set((state) => ({ level: state.level + 1 })),
  }
}))

// 💡 在组件中消费时:
const username = useUserStore((state) => state.username)
const { updateName } = useUserStore((state) => state.actions) // 单独拉取 actions

4. 配合 Immer 处理复杂的嵌套对象

在 React 和 Zustand 中,状态是不可变的(Immutable)。如果你要修改一个深层嵌套的对象,原生写法很痛苦:

// 原生写法:需要疯狂解构
set((state) => ({
  user: {
    ...state.user,
    profile: {
      ...state.user.profile,
      age: state.user.profile.age + 1
    }
  }
}))

Zustand 完美支持 immer 中间件,让你能用“修改指针”的直观写法去写不可变数据:

import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

const useTodoStore = create()(
  immer((set) => ({
    todos: [{ id: 1, text: 'Learn Zustand', done: false }],
    toggleTodo: (id) =>
      set((state) => {
        // 仿佛在直接修改数据,immer 会在底层帮你生成新的不可变对象
        const todo = state.todos.find((t) => t.id === id)
        if (todo) todo.done = !todo.done
      }),
  }))
)

5. 数据持久化(Middleware)

工业级应用经常需要把一部分状态(如 Token、用户偏好设置)存到 localStorage 中。Zustand 内置了 persist 中间件,一行代码搞定自动同步:

import { create } from 'zustand'
import { persist } from 'zustand/middleware'

const useAuthStore = create()(
  persist(
    (set) => ({
      token: null,
      setToken: (token) => set({ token }),
    }),
    {
      name: 'auth-storage', // localStorage 中的 key 名称
    }
  )
)


总结:你的心智模型

你可以把 Zustand 想象成在你的 React 应用旁边盖了一座“数据中央仓库”。

  • 组件想改数据?发个信使(调用 Action)去仓库改。
  • 组件想用数据?拉一根专线(Selector Hook)连到仓库对应的货架上。货架的东西变了,专线才会响。

你目前正在开发的 React 项目中,有哪些状态是打算尝试用 Zustand 来管理的吗?我们可以顺着你的具体业务场景来聊聊怎么设计这个 Store。

Zustand 的解法:独立于组件树的“状态发布中心”

Zustand 的核心设计理念非常简单:把状态从 React 的组件树里抽离出来,存放在一个独立的、全局的 JavaScript 对象中(我们称之为 Store)。

任何组件需要数据,直接去 Store 里“订阅”那一部分数据。当数据变动时,只有真正使用了该数据的组件才会重新渲染。高效、干净。


2. Zustand 的核心设计理念:大道至简

Zustand(德语中“状态”的意思)的设计哲学可以概括为三点:

  1. 单一数据源(Single Store)且不污染组件: 所有的全局状态和修改状态的方法(Actions),都写在同一个地方。
  2. 基于 Hooks 的原子化订阅(Selectors): 它是通过 React Hooks 的方式提供给组件使用的。组件可以通过“选择器”只精准订阅它关心的那一个变量。
  3. Actions 与 State 共存: 在 Zustand 中,数据(State)和改变数据的函数(Actions)是写在一起的,这让业务逻辑非常内聚。

从零写一个 Zustand Store

我们来看一下它有多直观:

import { create } from 'zustand'

// 1. 定义状态的类型(TypeScript 最佳实践)
interface BearState {
  bears: number
  increasePopulation: () => void
  removeAllBears: () => void
}

// 2. 创建一个全局的 Store
const useBearStore = create<BearState>((set) => ({
  bears: 0, // 状态(State)
  
  // 改变状态的方法(Actions)
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

在组件中使用它:

function BearCounter() {
  // 正确姿势:使用 Selector 精准订阅 bears
  const bears = useBearStore((state) => state.bears)
  return <h1>{bears} around here ...</h1>
}

function Controls() {
  // 获取修改状态的方法
  const increasePopulation = useBearStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}


3. Zustand 的高级设计:精准控制重新渲染

这是 Zustand 最牛的地方,也是它性能强大的秘密。

当你写 const bears = useBearStore((state) => state.bears) 时,Zustand 在底层做了一件事:它会监听整个 Store 的变化,但每次 Store 变化时,它会拿旧的 bears 和新的 bears 做对比(默认是 === 严格相等对比)。

  • 如果 bears 没变,即使 Store 里的其他变量(比如 user)变了,BearCounter 组件也绝对不会重新渲染

我们经常在 React 社区听到一个词:“不可变数据”(Immutability)。如果你觉得它模糊,是非常正常的,因为这和我们平时直觉上的“改数据”习惯完全相反。

用最通俗的一句话来解释:在 React 的世界里,你一旦创建了一个对象或数组,就永远不要去修改它的内部成员。如果你想改变它的值,你就必须用一块全新的内存,去重新生成一个全新的对象。

为了让你彻底搞懂,我们由浅入深,从底层原理到 React 为什么要这么做来拆解。


1. 现实生活中的比喻:改账本 vs 撕新纸

假设你有一张记事纸,上面写着你今天的待办事项:

  • 可变(Mutable): 你拿橡皮擦把第一行擦掉,改写成新的内容。纸还是那张纸(内存地址没变),但内容变了。
  • 不可变(Immutable): 你绝对不碰原来那张纸。你拿出一张全新的干净白纸,把不需要改的内容原封不动抄过来,把需要改的那一行写上新的内容。最后,你把旧纸扔了,手里拿的是新纸(内存地址彻底变了)。

在 React 中,你必须当那个“拿新纸”的人。


2. 核心区别:代码直观对比

我们在 JavaScript 里看看这两种操作的区别:

❌ 错误做法:可变操作(直接修改原对象)

const user = { name: 'Alex', age: 25 };

// 直接修改内部属性
user.age = 26; 

console.log(user); // { name: 'Alex', age: 26 }

在底层:user 指向的内存地址(比如 0x001)没有发生任何变化,改变的只是这块内存内部的数据。

正确做法:不可变操作(生成新对象)

const user = { name: 'Alex', age: 25 };

// 使用展开运算符(...)复制老数据,并覆盖需要修改的属性
const newUser = {
  ...user,
  age: 26
};

console.log(user);    // { name: 'Alex', age: 25 } (老对象完好无损)
console.log(newUser); // { name: 'Alex', age: 26 } (产生了一个全新的对象)

在底层:newUser 开辟了一块全新的内存地址(比如 0x002)。


3. React 为什么要强迫我们使用“不可变对象”?

这其实是 React 为了极致的性能而做出的设计权衡。

核心原因:浅比较(Shallow Comparison)的速度是极快的

React 的核心机制是:当状态(State)改变时,重新渲染组件。
那么 React 怎么知道状态有没有变呢?它需要对比“老状态”和“新状态”。

如果状态是一个拥有上百个属性的深度嵌套对象,React 如果要去挨个遍历每个属性看谁变了(深比较),那性能代价太高了,页面直接卡死。

所以 React 玩了个花招:它只做浅比较,也就是只对比对象的内存地址Object.is(oldState, newState) 或者 oldState === newState)。

  • 如果你用了可变操作(直接改内部):
oldState.age = 26;
// React 检查:oldState === oldState 结果是 true!地址没变!
// 结论:React 认为数据没变,拒绝重新渲染界面。你的界面就“卡死”不更新了。

  • 如果你用了不可变操作(传了新对象):
const newState = { ...oldState, age: 26 };
// React 检查:oldState === newState 结果是 false!地址变了!
// 结论:React 秒懂数据变了,立刻高效地重新渲染界面。

划重点: 不可变数据让 React 不需要关心对象里面到底有什么,它只需要看一眼“对象的引用地址变了没有”,就能决定要不要更新界面。这种比对只需要 1 毫秒。


4. Zustand / React 里的常见不可变操作套路

在实际写代码时,由于不能直接用 = 修改数组和对象,我们需要熟练使用以下几种语法:

1. 修改对象属性(使用 ... 展开运算符)

// Zustand 的 set 语法要求返回一个新对象
set((state) => ({
  user: { ...state.user, age: 26 } 
}))

2. 向数组添加一项(不要用 push,要用 ...

push 会改变原数组,不可用。

// ❌ 错误:state.list.push(newItem) 
//  正确:展开老数组,放入新项,生成新数组
set((state) => ({
  list: [...state.list, newItem] 
}))

3. 删除数组中的一项(使用 filter

filter 默认会返回一个全新的数组,完美符合不可变要求。

set((state) => ({
  // 过滤掉 id 为 2 的项,返回一个新数组
  list: state.list.filter(item => item.id !== 2) 
}))

4. 修改数组中的某一选项(使用 map

map 也会返回一个新数组。

set((state) => ({
  list: state.list.map(item => 
    item.id === targetId  
      ? { ...item, done: true } // 找到目标,用新对象替换
      : item                    // 不是目标,原封不动保留引用
  )
}))


5. 解放双手:Immer 是如何解决这个痛苦的?

正如你在学习 Zustand 最佳实践时看到的,当对象层级很深时,每一层都要写 { ...state } 会让人抓狂。

这时候 Immer 出现了。Immer 提供了一个暂存盘(我们称为 draft,即草稿)。它允许你像写原生 JS 那样去直接修改草稿,等修改完了,Immer 会在底层自动帮你把这些修改转化为一个全新的不可变对象。

// 有了 Immer,你不需要写任何 ... 运算符
set((state) => {
  // 在这里,你可以违背 React 的常理,直接去修改内部属性
  state.user.profile.settings.theme = 'dark'; 
  // Immer 会在后台默默地帮你搞定“撕新纸、抄数据”的全过程!
})

现在,你对“不可变对象”这个概念,心里有没有感觉更清晰一点了?

在 React 开发中,决定一个状态或逻辑应该写成 Custom Hook(自定义 Hook)Zustand Store(全局状态) 还是 Context(上下文),是每个开发者都会面临的核心架构问题。

我们可以从两个维度来拆解:“数据的生命周期(谁在用它)”“逻辑的本质(它是数据还是行为)”

为了让你在选型时不再纠结,我们梳理出了一个决策模型:


1. 核心定位与应用场景对比

我们先用一张表来看清它们的本质区别:

工具 它的本质是什么? 核心痛点/解决什么问题? 典型应用场景
Custom Hook(自定义 Hook) 逻辑的复用封装(状态是隔离的) 提取重复的代码逻辑(如窗口大小监听、请求发送)。不共享状态 封装表单验证、封装 WebSocket 连接、封装倒计时逻辑。
Zustand Store(全局状态) 独立于组件树的****单例数据中心 跨多层级、高频更新、需要极致性能的全局共享数据 用户登录信息、全站主题配置、复杂 3D 场景的节点数据、实时定位轨迹数据。
Context(局部上下文) 依赖注入工具(依然强依赖组件树) 某一个组件树局部分支内的数据共享,或作为组件库封装的纽带。 复杂低码表单内的跨组件联动、下拉菜单(Select/Option)组件库的内部通信。

2. 场景深度拆解:什么时候用哪个?

场景一:我有一段逻辑,很多地方都要用,但它们的数据不需要共享

  • 选择:Custom Hook
  • 设计理念: 自定义 Hook 的核心是复用逻辑,而不是复用数据。每次你在组件里调用 useMyHook(),它内部的 useState 都是完全独立的,互不影响。
  • 工程实例: 假设你要写一个“获取当前鼠标在 3D 画布中世界坐标”的逻辑。A 组件要用它来做悬浮提示,B 组件要用它来做点击判定。
// useMousePosition.ts (Custom Hook)
export function useMousePosition() {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const handleMove = (e) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener('mousemove', handleMove);
    return () => window.removeEventListener('mousemove', handleMove);
  }, []);
  return pos; // 返回逻辑处理后的结果
}

A 组件和 B 组件各自调用这个 Hook,它们在内存里会创建两个独立的事件监听器,数据互不干扰。


场景二:我有一堆数据,全站到处都在用,或者更新极其频繁

  • 选择:Zustand Store
  • 设计理念: 只要数据是全局的(全站只需要一份),或者需要精准控制重新渲染(高性能),毫不犹豫选择 Zustand。
  • 工程实例: 比如一个工业数字孪生大屏项目,里面包含:
  1. 顶部的用户个人信息栏。
  2. 左侧的设备实时数据列表。
  3. 中央的 3D 渲染画布(包含大量的坐标、选中状态更新)。

这时候,如果这些设备轨迹数据、选中状态放在 React Context 里,一秒钟更新 60 次,整个大屏的所有 UI 组件全都会卡死。而放在 Zustand Store 里,利用 Selector(选择器),只有负责渲染 3D 轨迹的组件会高频更新,左侧和顶部的文字组件纹丝不动。


场景三:我有一组数据,只在某一个局部页面或某一组嵌套组件内共享

  • 选择:React Context
  • 设计理念: Context 的本质是动态依赖注入。它的生命周期是跟随 Provider 组件的挂载和卸载的。如果你希望“当这个页面关闭时,里面的状态全部自动销毁”,或者“同一个组件在页面里实例化多次,每次内部都有独立的上下文”,这就是 Context 的主场。
  • 工程实例: 假设你在做一个复杂的“流程图配置编辑器”组件:
    这个编辑器由 <EditorProvider><Sidebar><Canvas> 等子组件组成。用户可能在同一个页面里同时打开两个流程图编辑器(左边对比右边)。
  • 如果你用 Zustand,它是全局单例,两个编辑器会抢同一份数据,直接乱套。
  • 如果你用 Context,左边和右边的编辑器分别被包裹在各自的 <EditorProvider> 里,它们的数据完美隔离,互不干扰,且关闭编辑器时内存自动释放。

3. 终极决策黄金法则(避坑指南)

如果你在写代码时卡住了,可以用这三个问题来做决定:

  1. 它到底要不要共享数据?
  • 不需要(只是代码长,想抽离复用) \(\rightarrow\) Custom Hook
  • 需要 \(\rightarrow\) 走向问题 2。
  1. 这个数据的范围是全站唯一,还是局部多实例?
  • 局部多实例(比如一个复杂的表格组件、一个弹窗内的多步骤表单) \(\rightarrow\) Context
  • 全站唯一(或绝大多数页面都要调取) \(\rightarrow\) 走向问题 3。
  1. 这个数据更新频繁吗?对性能敏感吗?
  • 更新不频繁,且纯属于配置型(如国际化语言包 ThemeContext、轻量级的全局权限认证) \(\rightarrow\) Context 或 Zustand 均可(若项目已有 Zustand,推荐直接用 Zustand 统一管理)。
  • 更新频繁,或者有很多组件订阅它 \(\rightarrow\) 坚决使用 Zustand Store

💡 进阶组合拳:Custom Hook + Store / Context

在实际大厂规范中,我们经常将它们组合使用。对外只暴露 Hook,对内隐藏 Store 或 Context 的实现细节

例如,不要在组件里直接读 Store:

// ❌ 不推荐:组件直接感知到全局 store 的存在
const username = useUserStore(state => state.username); 

//  推荐做法:封装成一个 Custom Hook
// useAuth.ts
export function useAuth() {
  const user = useUserStore(state => state.currentUser);
  const login = useUserStore(state => state.actions.login);
  
  const isAuthenticated = !!user;
  
  return { user, isAuthenticated, login };
}

// 在组件中直接使用,未来哪怕把 Zustand 换成别的,组件也无需修改
const { user, isAuthenticated } = useAuth();

你目前手头上的项目里,有没有哪个功能让你在“该用 Context 还是 Zustand”之间产生了动摇?你可以把具体的组件嵌套关系和数据流向告诉我,我们一起来做个就地拆解。

这几年 React 团队的动作非常大,甚至可以说是对 React 诞生以来开发习惯的一次颠覆。他们核心的进化方向非常明确:把心智负担从开发者身上拿走,交给机器和底层架构。

既然你对底层设计理念感兴趣,那我们就用通俗的语言,把这几年最轰动的几个核心概念(RFC、Compiler、StrictMode 进化,以及大名鼎鼎的 RSC)由浅入深地盘明白。


1. Compiler(React 编译器):再见,useMemouseCallback

这可能是这两年 React 社区最兴奋的变革。

痛点:手动写性能优化太痛苦了

在过去,为了防止组件无脑地重复渲染,我们必须小心翼翼地用 useMemo 缓存复杂计算,用 useCallback 缓存函数:

// 以前的写法:不仅代码丑陋,而且新手极易漏掉依赖项数组
const expensiveValue = useMemo(() => doSomething(a), [a]);
const handleClick = useCallback(() => console.log(b), [b]);

这种“手动挡”的操作不仅增加了开发者的心智负担,而且一旦依赖项写错,就会引发难以排查的 Bug。

进化:React Compiler(代号 Forget)

React 团队认为:既然“不可变对象”的规则是确定的,那么代码怎么优化,机器应该比人类更清楚。

React Compiler 本质上是一个 Babel/Vite 插件。它在你的代码编译阶段(也就是打包成浏览器运行的代码之前),自动分析你的抽象语法树(AST),并在底层自动帮你注入缓存逻辑。

  • 现在的体验: 你只需要像写普通 JS 那样自由地写代码,不需要写任何 useMemo
  • 编译后: 编译器会自动帮你把组件里的每一行表达式、每个对象都变成自动缓存的。
  • 总结: React 正在从“手动挡”走向“自动挡”。(注:截至 2026 年,React Compiler 已经在 React 19 中作为核心生态体系被广泛生产实践)。

2. StrictMode(严格模式):为“瞬移”和“并发”做准备

你可能在应用入口处见过 <StrictMode> 标签。这几年它的存在感越来越强,甚至让很多新手崩溃,因为它会让你的组件在开发环境下执行两次

为什么在开发环境下,useEffect 会触发两次?

很多同学发现,在 StrictMode 下,组件一挂载,useEffect 里的请求会发两次。这其实是 React 故意在“找茬”。

React 未来(以及现在正在普及的 Concurrent Mode 并发模式)有一个终极愿景:组件的渲染是可以被随时中断、恢复、甚至“离屏缓存(Offscreen)”的。

比如:用户在一个标签页和另一个标签页之间切换,React 希望能把不可见的页面组件“冻结”在内存里,切换回来时“秒开”,而不是重新销毁再创建。

为了实现这个愿景,你的组件必须具备极高的确定性:挂载(Mount)和卸载(Unmount)必须是干净、可预测、无副作用的。

useEffect(() => {
  const window = startTracking(); // 1. 创建了某种副作用
  
  return () => {
    window.stopTracking(); // 2. 必须在清理函数里彻底杀掉它!
  };
}, []);

StrictMode 的监控逻辑:

在开发模式下,StrictMode 会故意执行:挂载 \(\rightarrow\) 卸载 \(\rightarrow\) 重新挂载
如果你在“卸载”时没有写好清理函数(Cleanup),第二次挂载时就会暴露 Bug(比如定时器叠加、内存泄漏)。

  • 总结: StrictMode 不是一个功能,而是一个“考官”。它在开发阶段疯狂折磨你,是为了确保你的代码在线上高并发、动态切页面时稳如泰山。

3. RFC(Request for Comments):开源世界的“立法会”

RFC 并不是 React 独有的技术名词,它是开源社区(如 Rust, Vue, React, Ember 等)用来制定重大变更的“征求意见稿机制”。

当 React 团队(或者社区大牛)想要给 React 增加一个破坏性的新特性(比如当年的 Hooks,后来的 Server Components,或者全新的 use API)时,不能直接一拍脑袋就把代码合并了。他们需要走 RFC 流程:

  1. 写提案: 详细阐述“为什么要加这个功能”、“解决了什么痛点”、“API 长什么样”、“对老项目有什么破坏性”。
  2. 社区对线: 提案公开在 GitHub 的 reactjs/rfcs 仓库里,全世界的开发者(包括各大框架的作者、大厂架构师、普通开发者)都会在下面评论、吐槽、辩论。
  3. 修定与通过: 经过几轮撕扯和修改后,如果达成共识,该提案就会转为“已接受(Accepted)”,正式进入排期开发。
  • 总结: 了解 RFC 能让你看清 React 的未来。很多时候,一个新特性从 RFC 提出来到最后稳定落地,可能需要 2~3 年的时间。

4. 补充一个最重要的时代跨越:RSC(React Server Components)

聊最近几年的 React,绝对绕不开 RSC(服务端组件)。这是 React 诞生以来最大的架构范式转变。

以前的模式:客户端渲染(CSR)

浏览器下载一个极其精简的 HTML 和一堆巨大的 JS 文件,然后在用户的手机/电脑里运行 JS,动态生成 DOM 节点。

  • 缺点: 首屏加载慢,对 SEO 不友好;组件为了拿数据,要在前端发一堆请求(造成瀑布流延迟)。

现在的模式:RSC(Server Components)

React 将组件分成了两类:

  1. Server Components(服务端组件,默认): 它们只在服务器上运行。它们可以直接连接数据库、读取服务器文件。运行完后,生成的不是 HTML 字符串,而是一种轻量级的流式数据,传给浏览器。
  2. Client Components(客户端组件,带 "use client" 指令): 传统的 React 组件,在浏览器里运行,可以有 useStateuseEffect 和点击事件。

为什么这个概念很震撼?

  • 零打包体积(Zero Bundle Size): 如果你在 Server Component 里引用了一个巨大的 Markdown 解析库(比如 5MB),这个库完全不会被下载到用户的浏览器里。服务器运行完就扔了,用户端不需要多下载 1KB 的 JS。
  • 安全与速度: 你的数据库连接密码、鉴权逻辑安全地留在了服务器,前端不需要暴露任何内部接口。

梳理:这几年 React 的演进心智模型

如果把这几年的全景图串起来,你会发现 React 团队在下一盘大棋:

  • 通过 RFC 与全球顶级开发者切磋,规划出通往未来的路;
  • 通过 RSC 让框架把重活、脏活(读数据库、解压大文件)留在服务器上做;
  • 通过 Compiler 解决前端臭名昭著的重新渲染性能问题,解放开发者的双手;
  • 通过 StrictMode 强迫开发者写出无副作用的纯净组件,为未来更疯狂的并发、离屏渲染铺平道路。

这些新概念里,哪一个最颠覆你之前对“前端开发就是写写页面”的固有印象?

性能

React 为什么要引入 Fiber

在 React 15 及之前,组件的 reconciliation(协调)过程是同步、递归、不可中断的

  • 一旦开始更新(比如 setState),React 会一口气遍历整个组件树,计算出 Virtual DOM 的差异。
  • 这个过程会阻塞主线程,如果组件树很大或更新复杂,就会导致:
    • 页面卡顿(掉帧)
    • 用户交互无响应(如点击没反应)

🎯 问题本质:JavaScript 是单线程的,长时间运行的同步任务会阻塞 UI 渲染和用户输入。

解决方案:Fiber 架构(React 16+)

Fiber 是 React 16 引入的全新的协调引擎,核心目标是:

将渲染/更新过程拆分成可中断的小任务,让浏览器有机会处理高优先级任务(如用户输入、动画)。

Fiber 的关键特性:

  1. 增量渲染(Incremental Rendering)
    把 reconciliation 拆成多个小单元(fiber 节点),每个单元执行完后检查是否该让出控制权。

  2. 可中断与恢复
    如果有更高优先级任务(如用户点击),React 可以暂停当前更新,先处理紧急任务,之后再恢复。

  3. 优先级调度(Prioritization)
    不同更新有不同优先级(如 setState vs useTransition),Fiber 能动态调整执行顺序。

  4. 并发模式(Concurrent Mode)基础
    Fiber 为后续的并发特性(如 Suspense、Streaming SSR)打下基础。

Fiber 的本质:用“协作式调度”解决主线程阻塞问题。

posted @ 2026-07-04 14:46  tommao9925  阅读(11)  评论(0)    收藏  举报