第11期 | 为什么需要框架?从jQuery到React

系列:AI前端工程师·从零基础到拿到Offer
模块二开篇。欢迎来到前端工程化的世界。


🎯 今天你将学会

  • 理解前端框架演进的历史动力:每代框架解决了什么问题
  • 用同一个功能(Todo列表)对比原生JS/jQuery/Vue/React四种写法
  • 理解 React 的核心思想:声明式 UI 和组件化
  • 建立框架选型的判断力:什么项目用什么框架

📖 核心知识

一、历史背景:前端从"美化页面"到"构建应用"

2000年代:服务端渲染时代

那时候根本没有"前端工程师"这个职位——前端是后端顺带干的。HTML 由服务器拼好整页返回,浏览器只负责展示。JavaScript 只用来做一点点动画和验证。

2006年:jQuery 的出现

jQuery 解决了一个当时巨大的痛点:浏览器兼容性。那时候同一段代码在 IE、Firefox、Safari 里行为不一样,jQuery 封装了这些差异,提供统一的 API:

// 不用 jQuery:为不同浏览器写不同代码
var el = document.getElementById ? document.getElementById('box') : document.all['box'];

// 用 jQuery:一行搞定
$('#box').hide().fadeIn(300);

jQuery 统治前端长达10年。但随着 Web 应用越来越复杂,jQuery 的问题开始暴露:大量手动 DOM 操作,状态难以管理

2013年:React 的出现

Facebook 的工程师遇到了这个问题:他们的页面状态极其复杂(通知数量、消息列表、好友状态……),任何一处更新都可能影响多个 UI 组件,用 jQuery 手动同步这些状态简直是噩梦。

React 的核心思想解决了这个问题:不手动操作 DOM,告诉框架"我希望 UI 是什么样子",框架负责更新 DOM。


二、用同一个 Todo 列表对比四种写法

我们来实现同样的功能:显示 todo 列表,可以添加新 todo,可以标记完成。

写法一:原生 JavaScript(命令式)
// 我们在第04-06期已经很熟悉这种写法了
let todos = [
  { id: 1, text: '学 HTML', done: true },
  { id: 2, text: '学 CSS', done: false },
];

function renderTodos() {
  const ul = document.getElementById('todoList');
  ul.innerHTML = todos.map(todo => `
    <li>
      <input type="checkbox" ${todo.done ? 'checked' : ''}
             onchange="toggleTodo(${todo.id})">
      <span style="${todo.done ? 'text-decoration:line-through' : ''}">${todo.text}</span>
    </li>
  `).join('');
}

function toggleTodo(id) {
  const todo = todos.find(t => t.id === id);
  todo.done = !todo.done;
  renderTodos(); // 每次都要手动重新渲染
}

function addTodo(text) {
  todos.push({ id: Date.now(), text, done: false });
  renderTodos(); // 手动同步 UI
}

renderTodos(); // 初始渲染

问题: 每次状态变化都要手动调用 renderTodos();随着逻辑复杂,你可能忘了在某个地方调用,UI 就不同步了。

写法二:jQuery
// jQuery 风格:链式操作 DOM,但问题本质一样
$(function() {
  let todos = [];

  function render() {
    $('#todoList').empty();
    todos.forEach(todo => {
      const li = $('<li>').appendTo('#todoList');
      $('<input type="checkbox">').prop('checked', todo.done)
        .on('change', () => {
          todo.done = !todo.done;
          render(); // 还是手动 render
        }).appendTo(li);
      $('<span>').text(todo.text)
        .css('text-decoration', todo.done ? 'line-through' : '')
        .appendTo(li);
    });
  }

  $('#addBtn').on('click', () => {
    const text = $('#input').val().trim();
    if (text) {
      todos.push({ id: Date.now(), text, done: false });
      render();  // 手动 render
    }
  });
});

问题本质一样: jQuery 让 DOM 操作更方便了,但没有解决"状态 → UI 同步"的根本问题。

写法三:Vue 3
<template>
  <div>
    <input v-model="newTodo" @keyup.enter="addTodo" placeholder="新 Todo">
    <button @click="addTodo">添加</button>
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        <input type="checkbox" v-model="todo.done">
        <span :class="{ done: todo.done }">{{ todo.text }}</span>
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const newTodo = ref('');
const todos = ref([
  { id: 1, text: '学 HTML', done: true },
  { id: 2, text: '学 CSS', done: false },
]);

function addTodo() {
  if (newTodo.value.trim()) {
    todos.value.push({ id: Date.now(), text: newTodo.value, done: false });
    newTodo.value = '';
  }
}
</script>

变化: 没有任何手动 DOM 操作!你只需要修改 todos 这个数据,Vue 自动更新 UI。

写法四:React
import { useState } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([
    { id: 1, text: '学 HTML', done: true },
    { id: 2, text: '学 CSS', done: false },
  ]);
  const [input, setInput] = useState('');

  const addTodo = () => {
    if (!input.trim()) return;
    setTodos([...todos, { id: Date.now(), text: input, done: false }]);
    setInput('');
  };

  const toggleTodo = (id) => {
    setTodos(todos.map(t => t.id === id ? { ...t, done: !t.done } : t));
  };

  return (
    <div>
      <input
        value={input}
        onChange={e => setInput(e.target.value)}
        onKeyUp={e => e.key === 'Enter' && addTodo()}
        placeholder="新 Todo"
      />
      <button onClick={addTodo}>添加</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.done}
              onChange={() => toggleTodo(todo.id)}
            />
            <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
}

关键区别: 没有任何 document.querySelector,没有手动 render(),没有 innerHTML。你只是在描述"当 todos 是这个值时,UI 长什么样"——React 负责一切 DOM 操作。


三、React 的三个核心思想

1. 声明式(Declarative)
命令式(jQuery/原生JS):怎么做——"找到这个元素,改变它的文本,添加这个类"
声明式(React):要什么——"当 count 是 5 时,显示'5个任务'"

声明式更符合人类思维,bug 更少,代码更可预测。

2. 组件化(Component-Based)

一个复杂页面可以拆成很多独立的、可复用的组件:

// 每个组件像一个自定义 HTML 标签
function App() {
  return (
    <div>
      <Navbar />                    {/* 导航栏组件 */}
      <HeroSection />               {/* 主图区域组件 */}
      <ArticleGrid articles={data} /> {/* 文章列表组件,接收数据 */}
      <Footer />                    {/* 页脚组件 */}
    </div>
  );
}

组件可以复用:Navbar 在每个页面都用,改一处全部生效。

3. 单向数据流(One-Way Data Flow)

数据只从父组件流向子组件,不能反向。子组件想改数据,需要调用父组件传来的函数:

function Parent() {
  const [count, setCount] = useState(0);

  // 把数据和修改数据的函数都传给子组件
  return <Child count={count} onIncrement={() => setCount(count + 1)} />;
}

function Child({ count, onIncrement }) {
  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={onIncrement}>+1</button>
      {/* 子组件不能直接改 count,只能调用 onIncrement */}
    </div>
  );
}

单向数据流让数据变化可追踪,bug 更容易定位。


四、Virtual DOM:React 高效更新的秘密

React 不直接操作真实 DOM(操作 DOM 代价很高),而是先在内存里维护一个"Virtual DOM"(虚拟 DOM),每次状态变化后:

  1. 重新计算 Virtual DOM
  2. 对比新旧 Virtual DOM(Diffing 算法)
  3. 只把真正变化的部分同步到真实 DOM

这叫 Reconciliation(协调)

状态变化 → 重新渲染组件 → 得到新 Virtual DOM → Diff → 最小化 DOM 更新

理解 Virtual DOM 对面试很重要,但在日常开发中你不需要关心它——React 自动处理。


五、框架选型指南

场景 推荐 理由
独立前端工程师求职 React 市场需求最大,生态最丰富
国内中小厂 Vue 3 学习曲线低,文档完善
全栈/服务端渲染 Next.js(React)或 Nuxt(Vue) 内置 SSR/SSG
超轻量页面(无需组件化) 原生 JS 不引入框架负担
可交互内容展示 Svelte 编译时优化,包体积小
Angular 遗留项目 Angular 不要随便换框架

本课程选择 React 的原因:

  1. 国内外大厂普遍使用,面试需求最高
  2. 生态最丰富(Next.js、React Native、各类 UI 库)
  3. 掌握 React 后,Vue 上手很快(反之亦然)
  4. AI 应用开发(Vercel AI SDK、LangChain.js)基本都有 React 版本

六、React 生态全景

在深入学 React 之前,先知道它的生态:

React 核心
├── 状态管理
│   ├── useState / useReducer(内置)
│   ├── Context API(内置,跨组件传递)
│   ├── Zustand(轻量,推荐)
│   └── Redux Toolkit(复杂应用)
│
├── 路由
│   └── React Router(第12-20期会用到)
│
├── 服务端渲染框架
│   └── Next.js(全栈 React 应用首选)
│
├── UI 组件库
│   ├── shadcn/ui(现代,Tailwind CSS)
│   ├── MUI(Material Design)
│   └── Ant Design(国内常用)
│
├── 数据请求
│   ├── TanStack Query(服务端状态管理)
│   └── SWR(Vercel 出品)
│
└── 构建工具
    └── Vite(现代打包工具,第17期专讲)

你不需要全部学,按需取用。接下来几期我们先学核心。


🤖 AI协作实战

场景:让 AI 对比 React 和 Vue 的代码写法

我给 AI 的 Prompt:

请用 React 和 Vue 3 分别实现一个相同的功能:
一个带输入框的搜索过滤列表(输入关键词,实时过滤列表项)。

然后帮我对比:
1. 代码量和可读性
2. 状态管理方式的差异
3. 事件绑定语法的差异
4. 各自的学习曲线

不需要完整项目,只需要核心逻辑代码。

AI 的分析摘要(我整理的):

对比维度 React Vue 3
状态声明 const [val, setVal] = useState('') const val = ref('')
绑定值 value={val} onChange={e => setVal(e.target.value)} v-model="val"
计算值 const filtered = items.filter(...) 直接写 const filtered = computed(...)
模板语法 JSX(JS 中写 HTML) 独立 .vue 文件(template/script/style)
学习曲线 需要理解 JSX、hooks 思维 模板语法更接近 HTML,入门更友好

两者都很好用,我选 React 是因为市场需求和 AI 应用生态更大。


💻 动手练习

练习1(简单): 不用框架,纯原生 JS 实现:当 todos 数组变化时(添加/删除/切换),观察你需要手动调用多少次 render()。感受一下这种"手动同步"的繁琐。

练习2(阅读理解): 把本期第二节的四种 Todo 写法全部读一遍,对照着找到:

  • 每种写法里"添加 todo"的操作在哪里
  • "标记完成"触发了什么
  • "UI 更新"是如何发生的

练习3(准备工作): 下期我们开始写 React,提前安装好环境:

# 确认 Node.js 版本 >= 18
node --version

# 创建第一个 React 项目(下期用到)
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
# 打开 http://localhost:5173,看到 React 欢迎页就成功了

📌 本期要点

  1. 框架的本质:解决"状态变化时 UI 自动同步"的问题,而不是让 DOM 操作更方便
  2. React 三原则:声明式(描述要什么)+ 组件化(可复用单元)+ 单向数据流(数据可追踪)
  3. Virtual DOM:React 在内存里算好最小更新,再同步到真实 DOM,提高性能
  4. React vs Vue:两者思路相似,React 市场需求更大,Vue 入门更友好;掌握一个,另一个触类旁通
  5. 不要纠结选哪个:现在学 React,后面如果需要 Vue 一周就能上手

🔗 下期预告

第12期:React核心·JSX与组件

终于要写 React 了!下期从最基础的 JSX 语法开始,理解函数组件、Props、State,写出你第一个真正的 React 组件。

AI协作点:我们会让 AI 生成一批组件模板,然后拆解每一部分的意思——这是理解 React 最高效的方式之一。
如果你没有苹果电脑,需要上传ios到APPStore可以访问以下网站
iPA上传工具 - IPA解析与AppStore提交

更多推荐