Skip to content

React 技术演进史:从 jQuery 到 React 19

理解每次技术变革的动机,才能真正掌握 React 的设计哲学

演进时间线

2013 - React 诞生 ─── 虚拟 DOM、单向数据流

2015 - React 0.14 ─── 函数组件、拆分 react-dom

2016 - React 15 ─── Stack Reconciler、合成事件优化

2017 - React 16 ─── Fiber 架构 🔥 (重写核心)

2019 - React 16.8 ─── Hooks 🔥 (编程范式转变)

2020 - React 17 ─── 渐进升级、事件委托改变

2022 - React 18 ─── Concurrent Features 🔥

2024 - React 19 ─── Server Components、Actions、Compiler 🔥🔥

2024-2025 - React 19.x ─── Activity、useEffectEvent

1. 前 React 时代:jQuery 的困境 (2006-2013)

1.1 jQuery 统治时期

javascript
// jQuery 时代的典型代码
$('#button').click(function() {
    var name = $('#input').val();
    $('#display').text('Hello, ' + name);
    $('#display').addClass('highlight');
    if (name.length > 10) {
        $('#warning').show();
    } else {
        $('#warning').hide();
    }
});

1.2 暴露的问题

问题表现后果
命令式操作手动操作 DOM 的每一步代码冗长、易出错
状态分散状态存在 DOM 中难以追踪、同步困难
无组件化代码复用靠复制粘贴维护噩梦
数据流混乱任意位置修改任意 DOM调试困难

1.3 大型应用的崩溃

Facebook 2011 年的 Chat 功能:
- 多个地方显示未读消息数
- 任何一处更新都可能遗漏其他
- Bug: 打开聊天后未读数不同步
- 原因: 命令式更新无法保证一致性

核心矛盾: 命令式编程无法 Scale


2. React 诞生:声明式革命 (2013)

2.1 核心洞察

Facebook 工程师 Jordan Walke 的关键思考:

"如果 UI 是状态的函数,那么 UI = f(state)"

javascript
// 命令式 (jQuery)
$('#count').text(newCount);  // 告诉浏览器"怎么做"

// 声明式 (React)
<div>{count}</div>           // 告诉 React"要什么"

2.2 虚拟 DOM 的诞生

动机:直接操作 DOM 太慢,但声明式需要全量更新

解决方案:在 JS 层做 Diff,最小化 DOM 操作

State A ──→ Virtual DOM A ─┐
                           ├──→ Diff ──→ Patches ──→ Real DOM
State B ──→ Virtual DOM B ─┘

2.3 单向数据流

动机:双向绑定在复杂应用中造成数据流向不可预测

┌─────────────────────────────────────────────────┐
│           双向绑定的问题 (Angular 1.x)           │
├─────────────────────────────────────────────────┤
│  Model ◄──────► View ◄──────► Model             │
│         数据流向不明,调试困难                    │
└─────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────┐
│           单向数据流 (React)                     │
├─────────────────────────────────────────────────┤
│  State ──→ View ──→ Action ──→ State            │
│         数据流向清晰,可预测                      │
└─────────────────────────────────────────────────┘

2.4 解决的问题

问题React 的解决方案
命令式复杂度声明式 UI = f(state)
DOM 操作性能虚拟 DOM + Diff
数据流混乱单向数据流
代码复用组件化

3. Fiber 架构:可中断的革命 (2017, React 16)

3.1 Stack Reconciler 的问题

┌─────────────────────────────────────────────────┐
│         Stack Reconciler (React 15)              │
├─────────────────────────────────────────────────┤
│  渲染大组件树时:                                  │
│                                                 │
│  ┌─────────────────────────────────────────┐    │
│  │         递归调用栈 (同步执行)             │    │
│  │  render() → render() → render() → ...   │    │
│  │         不可中断! 100ms+ 卡顿             │    │
│  └─────────────────────────────────────────┘    │
│                                                 │
│  用户点击按钮 → 无响应 → 体验差                   │
└─────────────────────────────────────────────────┘

3.2 动机:让渲染可中断

核心问题:JS 是单线程,长任务会阻塞用户交互

javascript
// 问题场景
function BigList({ items }) {
    return items.map(item => <ComplexItem key={item.id} {...item} />);
    // 10000 个 item,渲染需要 500ms
    // 这 500ms 用户无法输入、点击、滚动
}

3.3 Fiber 的解决方案

关键创新:把递归改成循环,把调用栈改成链表

┌─────────────────────────────────────────────────┐
│           Fiber Reconciler (React 16+)           │
├─────────────────────────────────────────────────┤
│  时间切片 (Time Slicing):                        │
│                                                 │
│  ┌────┐┌────┐┌────┐┌────┐┌────┐               │
│  │工作 ││让出 ││工作 ││让出 ││工作 │ ...          │
│  │ 5ms││    ││ 5ms││    ││ 5ms│               │
│  └────┘└────┘└────┘└────┘└────┘               │
│        ↑                                        │
│    每帧预留时间给浏览器,保持响应                  │
└─────────────────────────────────────────────────┘

3.4 为什么用链表?

javascript
// 递归版本 - 信息在调用栈中,无法保存
function traverse(node) {
    process(node);
    node.children.forEach(traverse);  // 中断后丢失位置
}

// 链表版本 - 信息在 Fiber 节点中,可以保存
let current = root;
while (current && !shouldYield()) {
    process(current);
    current = getNext(current);  // 中断后可以继续
}

3.5 解决的问题

问题Fiber 的解决方案
长任务阻塞时间切片,可中断
无优先级Lane 模型,优先级调度
无法恢复链表结构,保存进度

4. Hooks:函数式编程的胜利 (2019, React 16.8)

4.1 Class 组件的问题

javascript
// 问题 1: 逻辑难以复用
class UserList extends Component {
    componentDidMount() {
        this.fetchUsers();
        window.addEventListener('resize', this.handleResize);
    }
    componentWillUnmount() {
        window.removeEventListener('resize', this.handleResize);
    }
    // 相关逻辑分散在不同生命周期中
}

// 问题 2: HOC 嵌套地狱
export default withRouter(
    withTheme(
        withAuth(
            withAnalytics(UserList)
        )
    )
);
// <WithRouter><WithTheme><WithAuth><WithAnalytics><UserList/></...>

4.2 动机

Class 组件问题表现
逻辑复用难HOC、Render Props 嵌套地狱
生命周期分散相关逻辑分散在 didMount/willUnmount
this 困扰绑定 this、箭头函数、bind
难以优化Class 难以 tree-shaking

4.3 Hooks 的解决方案

javascript
// 逻辑复用: 自定义 Hook
function useWindowSize() {
    const [size, setSize] = useState({ width: 0, height: 0 });
    
    useEffect(() => {
        const handler = () => setSize({
            width: window.innerWidth,
            height: window.innerHeight
        });
        window.addEventListener('resize', handler);
        return () => window.removeEventListener('resize', handler);
    }, []);  // 相关逻辑集中在一起!
    
    return size;
}

// 使用: 组合而非嵌套
function UserList() {
    const size = useWindowSize();
    const theme = useTheme();
    const auth = useAuth();
    // 扁平的、可组合的
}

4.4 设计哲学转变

Class 组件时代:
  继承 → 生命周期 → 面向对象

Hooks 时代:
  组合 → 副作用隔离 → 函数式编程

4.5 解决的问题

问题Hooks 的解决方案
逻辑复用自定义 Hooks,组合模式
生命周期分散useEffect 集中相关逻辑
this 绑定函数组件,无 this
代码体积更好的 tree-shaking

5. Concurrent Features:用户体验优先 (2022, React 18)

5.1 问题:CPU 密集型更新

javascript
// 搜索场景
function Search() {
    const [query, setQuery] = useState('');
    const [results, setResults] = useState([]);
    
    const handleChange = (e) => {
        setQuery(e.target.value);      // 需要立即响应
        setResults(search(e.target.value));  // 可能很慢
    };
    
    // 问题: 搜索结果计算阻塞了输入框响应
}

5.2 动机:区分紧急与非紧急更新

更新类型示例期望
紧急输入、点击、按键立即响应
非紧急搜索结果、列表渲染可以延迟

5.3 解决方案:Concurrent Rendering

javascript
function Search() {
    const [query, setQuery] = useState('');
    const [isPending, startTransition] = useTransition();
    
    const handleChange = (e) => {
        setQuery(e.target.value);  // 紧急更新:同步
        
        startTransition(() => {
            setResults(search(e.target.value));  // 非紧急:可中断
        });
    };
    
    return (
        <>
            <input value={query} />
            {isPending ? <Spinner /> : <Results />}
        </>
    );
}

5.4 Automatic Batching

javascript
// React 17: setTimeout 内每次 setState 都触发渲染
setTimeout(() => {
    setCount(c => c + 1);  // 渲染
    setFlag(f => !f);       // 渲染
}, 0);

// React 18: 自动批处理,只渲染一次
setTimeout(() => {
    setCount(c => c + 1);
    setFlag(f => !f);
    // 只有一次渲染!
}, 0);

5.5 解决的问题

问题Concurrent 的解决方案
所有更新同优先级Lane 模型区分优先级
紧急更新被阻塞useTransition 降级非紧急更新
手动批处理Automatic Batching
Loading 状态管理Suspense + SuspenseList

6. Server Components:全栈 React (2024, React 19)

6.1 传统 CSR 的问题

┌─────────────────────────────────────────────────┐
│              传统 CSR 流程                        │
├─────────────────────────────────────────────────┤
│  1. 下载 HTML (空壳)                             │
│  2. 下载 JS Bundle (可能很大)                    │
│  3. 执行 JS                                     │
│  4. 发起数据请求                                 │
│  5. 渲染 UI                                     │
│                                                 │
│  问题:                                          │
│  - Bundle 越来越大 (组件库、状态库、工具库)       │
│  - 瀑布流请求 (JS → API → 渲染)                  │
│  - 首屏白屏时间长                                │
└─────────────────────────────────────────────────┘

6.2 动机:减少客户端 JS

传统问题数据
平均 JS Bundle300-500KB (gzip)
依赖库占比60-70%
首屏时间3-5s (慢网络)

6.3 Server Components 的解决方案

┌─────────────────────────────────────────────────┐
│           Server Components 架构                 │
├─────────────────────────────────────────────────┤
│                                                 │
│  Server Component (不发送到客户端)               │
│  ├── 直接访问数据库                              │
│  ├── 零 Bundle Size                            │
│  └── 渲染成 HTML + Flight 数据                  │
│                                                 │
│  Client Component ('use client')                │
│  ├── 需要交互的部分                              │
│  ├── 发送到客户端                               │
│  └── 可以使用 useState/useEffect               │
│                                                 │
└─────────────────────────────────────────────────┘
jsx
// Server Component - 不发送 JS
async function BlogPost({ id }) {
    const post = await db.query(`SELECT * FROM posts WHERE id = ${id}`);
    // 直接访问数据库!
    
    return (
        <article>
            <h1>{post.title}</h1>
            <p>{post.content}</p>
            <LikeButton postId={id} />  {/* Client Component */}
        </article>
    );
}

// Client Component - 发送 JS
'use client';
function LikeButton({ postId }) {
    const [liked, setLiked] = useState(false);
    return <button onClick={() => setLiked(true)}>Like</button>;
}

6.4 Server Actions:简化数据变更

jsx
// 传统方式: 需要 API 路由
// pages/api/create-post.js
// fetch('/api/create-post', { method: 'POST', body: ... })

// Server Actions: 直接在组件中定义
async function createPost(formData) {
    'use server';
    await db.insert('posts', {
        title: formData.get('title'),
        content: formData.get('content')
    });
    revalidatePath('/posts');
}

function CreatePostForm() {
    return (
        <form action={createPost}>
            <input name="title" />
            <textarea name="content" />
            <button type="submit">Create</button>
        </form>
    );
}

6.5 解决的问题

问题RSC 的解决方案
Bundle 过大Server Component 零 Bundle
API 层模板代码Server Actions 直接变更
数据获取瀑布服务端获取,Streaming 传输
首屏性能服务端渲染,增量传输

7. React Compiler:告别手动优化 (2024-2025)

7.1 手动优化的心智负担

javascript
// 开发者每天的灵魂拷问
function Component({ data, onClick }) {
    // 这个需要 useMemo 吗?
    const processed = processData(data);
    
    // 这个需要 useCallback 吗?
    const handleClick = () => {
        onClick(processed);
    };
    
    // 子组件需要 memo 吗?
    return <Child data={processed} onClick={handleClick} />;
}

// 过度优化: 到处都是 useMemo/useCallback
// 遗漏优化: 性能问题难以定位
// 错误优化: 依赖数组写错

7.2 动机:自动化优化

问题表现
心智负担需要时刻考虑是否需要优化
容易出错依赖数组遗漏导致 Bug
过度优化不必要的 memo 增加复杂度
难以教学新手不知道何时优化

7.3 React Compiler 的解决方案

javascript
// 编译前 (你写的代码)
function Component({ data, onClick }) {
    const processed = processData(data);
    const handleClick = () => onClick(processed);
    return <Child data={processed} onClick={handleClick} />;
}

// 编译后 (React Compiler 自动生成)
function Component({ data, onClick }) {
    const $ = useMemoCache(3);
    
    let processed;
    if ($[0] !== data) {
        processed = processData(data);
        $[0] = data;
        $[1] = processed;
    } else {
        processed = $[1];
    }
    
    let handleClick;
    if ($[2] !== onClick || $[1] !== processed) {
        handleClick = () => onClick(processed);
        $[2] = onClick;
        $[3] = handleClick;
    } else {
        handleClick = $[3];
    }
    
    return <Child data={processed} onClick={handleClick} />;
}

7.4 解决的问题

问题Compiler 的解决方案
手动优化负担自动分析依赖,自动记忆化
依赖数组错误编译时静态分析,不会出错
过度/遗漏优化精确优化需要的部分
性能一致性所有组件自动优化

8. 演进规律总结

8.1 每次变革的共同点

┌─────────────────────────────────────────────────┐
│              React 演进的核心驱动力               │
├─────────────────────────────────────────────────┤
│                                                 │
│  1. 降低开发者心智负担                            │
│     jQuery → React: 不用手动操作 DOM             │
│     Class → Hooks: 不用管理 this                │
│     手动 memo → Compiler: 不用考虑优化           │
│                                                 │
│  2. 提升用户体验                                 │
│     Stack → Fiber: 不卡顿                       │
│     Sync → Concurrent: 响应优先                 │
│     CSR → RSC: 更快首屏                         │
│                                                 │
│  3. 拥抱函数式编程                               │
│     Class → Function: 函数组件                  │
│     Mutation → Immutable: 不可变数据            │
│     命令式 → 声明式: 描述结果而非过程             │
│                                                 │
└─────────────────────────────────────────────────┘

8.2 技术演进对应的问题

版本解决的核心问题引入的新概念
React 诞生DOM 操作复杂、状态管理混乱虚拟 DOM、组件化、单向数据流
React 16长任务阻塞、无法中断Fiber、时间切片、优先级
React 16.8逻辑复用难、Class 臃肿Hooks、函数组件
React 18所有更新同优先级Concurrent、useTransition
React 19Bundle 大、API 层繁琐RSC、Server Actions
Compiler手动优化心智负担自动记忆化

8.3 未来方向

当前趋势:
├── 全栈 React (RSC + Server Actions)
├── 自动优化 (React Compiler)
├── 更好的 DX (开发者体验)
└── 跨平台统一 (React Native 新架构)

可能的演进:
├── Signals? (更细粒度响应式)
├── 更智能的编译优化
├── 更紧密的服务端集成
└── AI 辅助开发?

9. 面试高频问题

Q1: 为什么 React 要从 Stack 架构改为 Fiber?

:Stack Reconciler 使用递归遍历,一旦开始无法中断。当组件树很大时,会长时间占用主线程,导致用户交互卡顿。Fiber 架构将递归改为可中断的循环,通过时间切片在每帧预留时间给浏览器,保持界面响应。

Q2: Hooks 解决了 Class 组件的什么问题?

  1. 逻辑复用:HOC/Render Props 嵌套地狱 → 自定义 Hooks 组合
  2. 关注点分离:生命周期分散逻辑 → useEffect 集中相关逻辑
  3. this 绑定:Class 的 this 困扰 → 函数组件无 this
  4. 代码体积:Class 难以优化 → 函数更易 tree-shaking

Q3: React 18 的 Concurrent 特性解决了什么问题?

:解决了所有更新同等优先级的问题。通过 useTransition 可以将非紧急更新标记为低优先级,让用户输入等紧急更新优先响应,提升用户体验。

Q4: Server Components 的核心价值是什么?

  1. 减少 Bundle:Server Component 不发送 JS 到客户端
  2. 简化数据获取:直接在组件中访问数据库,无需 API 层
  3. 提升首屏性能:服务端渲染 + Streaming 传输

Q5: React Compiler 会改变什么?

:开发者不再需要手动使用 useMemouseCallbackmemo 进行优化。编译器会静态分析代码,自动在需要的地方插入记忆化逻辑,消除了手动优化的心智负担和出错可能。

Q6: React 的演进体现了什么设计哲学?

  1. 声明式优于命令式:描述"要什么"而非"怎么做"
  2. 组合优于继承:Hooks 组合代替 Class 继承
  3. 用户体验优先:Fiber、Concurrent 都是为了更好的用户体验
  4. 降低开发者负担:每次演进都在减少需要手动处理的事情

前端面试知识库