跳到主要内容
预计阅读 35 分钟

初识React —— 为什么全世界都在用它

2013年,Facebook开源了一个用于构建用户界面的JavaScript库。没有人想到,这个当初被不少人嘲笑”在JavaScript里写HTML太疯狂了”的项目,会在十多年后成为全球最流行的前端框架之一,彻底改变了我们构建Web应用的方式。这个项目就是React。

📋 开篇自测:你已经知道多少?

  1. 你能说出React和jQuery在构建UI时的根本区别吗?
  2. 你听说过”虚拟DOM”吗?能用自己的话解释它为什么存在吗?
  3. 你知道JSX是什么,以及它和普通HTML有什么不同吗?

一、从jQuery到React——前端开发的进化之路

1.1 最原始的做法:直接操作DOM

如果你写过原生JavaScript,一定对这样的代码不陌生:

// 找到按钮,绑定点击事件
document.getElementById('myButton').addEventListener('click', function() {
  // 找到显示数字的元素
  var countEl = document.getElementById('count');
  // 读取当前值,加一,再写回去
  var current = parseInt(countEl.innerText);
  countEl.innerText = current + 1;
});

这段代码做的事情很简单——点击按钮,数字加一。但你仔细看会发现,我们的代码本质上是在”命令”浏览器:先找到这个元素,再读它的内容,然后改成新的值,最后塞回去。每一步都要手把手告诉浏览器怎么做。

当页面只有一个按钮和一个数字时,这没什么问题。但想象一下,你正在开发一个电商购物车页面——商品列表、数量增减、价格计算、优惠券应用、库存检查……每一次用户操作,你都要精确地知道该更新页面上的哪些元素,漏掉任何一个就是bug。

这就好比你是一个餐厅经理,每来一桌客人你都要亲自跑过去:先把菜单放到桌上,再去厨房通知厨师,然后盯着出菜口等菜好了端过去,最后还要记得去收银台更新账单。客人少的时候你能忙得过来,客人一多你就手忙脚乱了。

1.2 jQuery时代:好用但不够

jQuery的出现让DOM操作变得简洁了很多。$('#count').text(current + 1) 一行代码就搞定了。它就像是给你配了一群训练有素的服务员——找元素更快了,操作更方便了。

但问题的本质没有变:你仍然在手动管理页面上每一个元素的状态。

随着Web应用越来越复杂——单页应用(SPA)兴起,前端需要处理的交互逻辑呈爆炸式增长——jQuery这种”命令式”的模式开始吃力了。代码里充斥着大量的DOM查找和修改操作,逻辑和视图纠缠在一起,维护起来就像解一团乱麻。

1.3 React的思路:别告诉我怎么改,告诉我你要什么

React提出了一种截然不同的思路——声明式编程

用React写同样的计数器:

function Counter() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <p>当前计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

注意到区别了吗?这段代码里没有任何直接操作DOM的语句。你不需要告诉React”找到p标签,把它的内容改成新数字”。你只需要声明:“当count是0的时候,页面长这样;当count是1的时候,页面长那样。“至于具体怎么更新页面上的元素,React会自动帮你搞定。

回到餐厅的比喻:React就像是把你从”事必躬亲的经理”变成了”只需要画图纸的设计师”。你只需要画好”有3桌客人时餐厅该长什么样”的图纸,具体怎么搬桌子、摆餐具、上菜,全部交给一套自动化系统去做。

这就是从jQuery到React的核心跨越:从”命令式”到”声明式”,从”我来告诉你怎么改”到”我来告诉你我要什么”。

🤔 想一想 假设你正在开发一个即时聊天应用,当新消息到来时需要更新消息列表、未读计数、联系人排序等多个UI元素。用jQuery的命令式思维和React的声明式思维,分别会怎么处理这个场景?哪种方式更不容易出bug?


二、React的设计哲学——三根支柱撑起整个大厦

React之所以能在众多框架中脱颖而出,不是因为某个单一特性,而是因为它背后有一套深思熟虑的设计哲学。理解这些哲学,比记住任何API都重要。

2.1 声明式:描述结果,而非过程

我们在上一节已经感受过声明式编程的威力。这里再用一个生活中的例子来加深理解。

命令式就像你对出租车司机说:“先直行200米,在第二个路口左转,然后上高架桥,在第三个出口下来,再右转100米到达。“你需要精确描述每一步操作。

声明式就像你对出租车司机说:“去人民广场。“至于怎么开、走哪条路、要不要上高架,你不需要关心,司机自己会搞定。

React的声明式体现在:你用JSX描述UI在任意状态下应该长什么样,React负责高效地将实际DOM更新到与你描述的状态一致。数据变了,你不需要手动去操纵DOM,只需要更新数据,React自动帮你完成”从旧界面到新界面”的所有变更。

2.2 组件化:像搭积木一样建造应用

打开任何一个现代网站——比如微博或知乎——你看到的页面虽然复杂,但仔细观察会发现,它是由很多”独立的积木块”拼起来的:导航栏是一块、搜索框是一块、推荐列表是一块、每条动态又是一块、评论区也是一块。

React将这种直觉变成了代码的组织方式。每一块积木就是一个组件(Component),每个组件管好自己的那一小块UI和逻辑,然后像搭积木一样组合起来,形成完整的页面。

function App() {
  return (
    <div>
      <Navbar />
      <SearchBar />
      <FeedList />
      <Sidebar />
    </div>
  );
}

组件化带来了三个巨大的好处:

  1. 可复用:写好一个Button组件,整个应用甚至多个项目都能用
  2. 可维护:出bug了只需要定位到对应的组件,不用在几千行的文件里大海捞针
  3. 可协作:团队成员可以各自负责不同的组件,互不干扰

2.3 一次学习,到处编写

React的官方口号之一是”Learn Once, Write Anywhere”(一次学习,到处编写)。注意它说的不是”Write Once, Run Anywhere”(一次编写,到处运行),而是强调思维模式的通用性

学会了React的组件化思维和声明式编程方式后:

  • React DOM可以构建Web应用
  • React Native可以构建iOS和Android原生应用
  • React Three Fiber可以构建3D场景
  • Ink甚至可以构建命令行界面(CLI)应用

底层的渲染目标不同,但编程模型完全一样。你写的组件、管理状态的方式、思考UI的方式都是通用的。这意味着你在React上投入的学习时间,回报远不止于Web开发。


三、React的核心概念预览——四块基石

在正式动手之前,我们先用最直观的方式了解React的四个核心概念。这些概念会在后续章节中深入展开,这里只需要建立一个大致的心智模型。

3.1 JSX:在JavaScript里写”HTML”

第一次看到React代码时,很多人会感到困惑:

const element = <h1>你好,React!</h1>;

这是HTML?还是JavaScript?

答案是:都不是。 这是JSX(JavaScript XML),一种React发明的语法扩展。它让你可以在JavaScript代码中用类似HTML的方式来描述UI结构。

JSX看起来像HTML,但本质上是JavaScript。在编译阶段,每一段JSX都会被转换成JavaScript函数调用:

// 上面那行JSX,实际上等价于:
const element = React.createElement('h1', null, '你好,React!');

为什么React要发明这种”混搭”的写法?因为UI本质上就是和逻辑紧密耦合的。用户点击按钮时发生什么(逻辑)决定了屏幕上显示什么(UI)。React认为与其把它们拆散到不同的文件里人为分离,不如把相关的逻辑和视图放在一起,以组件为单位来组织代码。

JSX还有一些和HTML的关键区别,后续章节会详细讲解,这里先记住两个:

  • HTML的class属性在JSX中写成className(因为class是JavaScript的保留字)
  • JSX中可以用花括号{}嵌入任何JavaScript表达式
const name = '小明';
const element = <h1 className="greeting">你好,{name}!</h1>;

3.2 虚拟DOM:先在草稿纸上改,再一次性誊写

直接操作浏览器的DOM是昂贵的。每次修改DOM,浏览器可能都需要重新计算布局、重新绘制页面。如果你的应用每秒要更新很多次(比如一个实时股票行情页面),频繁的DOM操作会导致页面卡顿。

React的解决方案是引入一层”缓冲”——虚拟DOM(Virtual DOM)。虚拟DOM是一棵用普通JavaScript对象构建的树,它是真实DOM的轻量级副本。

更新过程是这样的:

  1. 当数据变化时,React先在内存中用虚拟DOM构建一棵”新树”
  2. 将”新树”与”旧树”进行对比(这个过程叫Diff
  3. 计算出最小的变更集
  4. 一次性将这些变更应用到真实DOM上

这就好比你在修改一篇文章。你不会直接在印刷好的书上用涂改液一个字一个字地改——那样又慢又难看。你会先在电脑上修改电子稿,所有改动确认后再重新打印。虚拟DOM就是那份”电子稿”。

3.3 组件:一切皆积木

React中的组件就是一个JavaScript函数(或类),它接收输入(叫做props),返回描述UI的JSX:

function Welcome(props) {
  return <h1>你好,{props.name}!</h1>;
}

// 使用这个组件
<Welcome name="小明" />

组件可以嵌套组件,组件可以复用组件,整个应用就是一棵由组件构成的树。最顶层是App组件,往下逐级分解成更小的组件:

App
├── Header
│   ├── Logo
│   └── Navigation
├── MainContent
│   ├── ArticleList
│   │   ├── ArticleCard
│   │   └── ArticleCard
│   └── Pagination
└── Footer

3.4 单向数据流:水往低处流

在React中,数据的流动方向是严格单向的:从父组件流向子组件,通过props传递,就像水往低处流一样。

function Parent() {
  const [message, setMessage] = React.useState('你好');
  return <Child text={message} />;
}

function Child(props) {
  return <p>{props.text}</p>;
}

子组件不能直接修改父组件传过来的props。如果子组件需要影响父组件的状态,必须通过父组件传下来的回调函数来实现。

这种”单行道”的设计让数据流向变得可预测。当界面出现bug时,你只需要沿着数据流的方向去排查——数据是从哪来的?经过了哪些组件?在哪一步变成了错误的值?

如果数据能在组件之间随意流动(双向数据流),排查问题就像在迷宫里找路——你根本不知道那个错误的值是从哪个方向窜过来的。

🤔 想一想 假设你正在开发一个待办事项应用,有一个输入框组件和一个列表组件。按照React单向数据流的思想,待办事项的数据应该放在哪里管理?输入框组件如何将新增的事项通知给列表组件?


四、开发环境搭建——万事俱备,只欠东风

理论讲了不少,是时候动手了。React开发环境的搭建非常简单,只需要两步:安装Node.js,然后用脚手架工具创建项目。

4.1 安装Node.js

React本身运行在浏览器里,但开发阶段的各种工具(编译JSX、打包代码、启动开发服务器)都依赖Node.js环境。

前往 https://nodejs.org 下载**LTS(长期支持)**版本并安装。安装完成后,打开终端验证:

node --version   # 输出类似 v20.x.x
npm --version    # 输出类似 10.x.x

看到版本号就说明安装成功了。

4.2 用Vite创建React项目(推荐)

目前创建React项目最流行的方式是使用Vite。Vite是一个新一代的前端构建工具,启动速度极快,开发体验极好。

在终端中执行:

npm create vite@latest my-first-react -- --template react

然后按照提示操作:

cd my-first-react
npm install
npm run dev

几秒钟后,终端会显示一个本地地址(通常是 http://localhost:5173),在浏览器中打开它,你会看到一个旋转的React Logo和一个计数器按钮——这就是你的第一个React应用!

4.3 项目结构一览

让我们看看Vite帮我们生成了什么:

my-first-react/
├── node_modules/       # 第三方依赖(不需要手动修改)
├── public/             # 静态资源(图片、图标等)
├── src/                # 源代码目录(你的主战场)
│   ├── App.jsx         # 根组件
│   ├── App.css         # 根组件的样式
│   ├── main.jsx        # 应用入口文件
│   ├── index.css       # 全局样式
│   └── assets/         # 资源文件
├── index.html          # HTML模板
├── package.json        # 项目配置和依赖声明
└── vite.config.js      # Vite配置文件

其中最关键的是src/main.jsx——这是整个应用的入口:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

这段代码做了一件事:找到HTML页面中idroot的元素,然后把App组件渲染到里面去。这就是React”挂载”到页面上的方式。

4.4 配置编辑器

推荐使用VS Code并安装以下扩展:

  • ES7+ React/Redux/React-Native snippets:React代码片段,让你少敲很多重复代码
  • Prettier:代码格式化工具,统一代码风格
  • ESLint:代码质量检查,帮你发现潜在问题

五、第一个React应用——从Hello World到计数器

5.1 Hello World

打开src/App.jsx,把里面的内容全部替换成:

function App() {
  return (
    <div>
      <h1>你好,React!</h1>
      <p>这是我的第一个React应用。</p>
    </div>
  );
}

export default App;

保存文件,浏览器会自动刷新(这是Vite的热更新功能),你会看到页面上显示了”你好,React!”。

恭喜!你已经写出了第一个React组件。让我们拆解一下这段代码:

  • function App() —— 定义了一个名为App的函数组件
  • return (...) —— 返回一段JSX,描述这个组件的UI
  • export default App —— 把这个组件导出,让其他文件可以引用它

5.2 动态内容:用花括号嵌入表达式

JSX中的花括号{}是JavaScript世界和HTML世界之间的桥梁。你可以在其中放入任何JavaScript表达式:

function App() {
  const name = '小明';
  const now = new Date();
  const hour = now.getHours();

  let greeting;
  if (hour < 12) {
    greeting = '上午好';
  } else if (hour < 18) {
    greeting = '下午好';
  } else {
    greeting = '晚上好';
  }

  return (
    <div>
      <h1>{greeting},{name}!</h1>
      <p>现在是 {now.toLocaleTimeString()}。</p>
      <p>2 + 3 = {2 + 3}</p>
    </div>
  );
}

export default App;

注意:花括号里只能放表达式(能产生值的代码),不能放语句(如if/for)。这就是为什么上面的例子中,if判断放在了JSX外面。

5.3 实现一个计数器

现在让我们来写一个有交互功能的组件——计数器。这需要引入React中最重要的概念之一:状态(State)

import { useState } from 'react';

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

  return (
    <div style={{ textAlign: 'center', marginTop: '50px' }}>
      <h1>计数器</h1>
      <p style={{ fontSize: '48px', margin: '20px 0' }}>{count}</p>
      <button onClick={() => setCount(count + 1)} style={{ marginRight: '10px' }}>
        +1
      </button>
      <button onClick={() => setCount(count - 1)} style={{ marginRight: '10px' }}>
        -1
      </button>
      <button onClick={() => setCount(0)}>
        重置
      </button>
    </div>
  );
}

export default Counter;

src/App.jsx的内容替换成上面的代码,保存后你会在浏览器中看到一个能点击的计数器。

让我们逐行理解关键代码:

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

这行代码使用了React的useState Hook(钩子函数)。它做了三件事:

  1. 创建一个名为count的状态变量,初始值为0
  2. 创建一个名为setCount的函数,用来更新count的值
  3. 返回这个数组,我们用解构赋值来接收

onClick={() => setCount(count + 1)}

当按钮被点击时,调用setCount并传入新的值。React检测到状态变化后,会自动重新渲染组件——也就是重新执行Counter函数,生成新的JSX,然后高效地更新DOM。

整个过程中,你没有写一行直接操作DOM的代码。你只是说了”count变成新值”,React就自动把页面更新好了。这就是声明式编程的威力。

5.4 拆分组件:让代码更有条理

随着应用变复杂,把所有东西塞在一个组件里显然不是好主意。让我们把计数器拆分成更小的组件:

import { useState } from 'react';

// 显示数字的组件
function Display({ value }) {
  return <p style={{ fontSize: '48px', margin: '20px 0' }}>{value}</p>;
}

// 按钮组件
function ActionButton({ label, onClick }) {
  return (
    <button onClick={onClick} style={{ margin: '0 5px', padding: '8px 16px' }}>
      {label}
    </button>
  );
}

// 主组件:组合上面的组件
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div style={{ textAlign: 'center', marginTop: '50px' }}>
      <h1>计数器</h1>
      <Display value={count} />
      <div>
        <ActionButton label="+1" onClick={() => setCount(count + 1)} />
        <ActionButton label="-1" onClick={() => setCount(count - 1)} />
        <ActionButton label="重置" onClick={() => setCount(0)} />
      </div>
    </div>
  );
}

export default Counter;

看到了吗?DisplayActionButton各自负责一件事情。Counter组件像一个”组装车间”,把小零件组合成完整的产品。这就是React组件化思维的日常实践。

🤔 想一想 在上面拆分后的代码中,ActionButton组件完全不知道count的存在——它只知道”被点击时执行一个函数”。这种设计有什么好处?如果未来你要在另一个完全不同的页面中复用这个按钮组件,需要做任何修改吗?


六、React生态全景速览——不只是一个库

React本身只负责UI渲染这一件事。但围绕React,社区发展出了一个庞大且成熟的生态系统,覆盖了现代Web开发的方方面面。这里先给你画一张”全景地图”,让你对即将进入的世界有个整体认知。

6.1 路由:让单页应用拥有多个”页面”

传统网站每点击一个链接就向服务器请求一个新的HTML页面。React构建的是单页应用(SPA)——页面从头到尾只有一个HTML文件,“跳转页面”实际上只是在同一个页面内切换显示的组件。

React Router 是React生态中最主流的路由库,它让你可以定义URL和组件之间的映射关系:

// 伪代码示意
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="/products/:id" element={<ProductDetail />} />
</Routes>

用户访问/about时看到About组件,访问/products/42时看到42号商品的详情页。浏览器地址栏会正常变化,前进后退也正常工作,但实际上页面并没有刷新。

6.2 状态管理:当组件间需要共享数据

当应用变得复杂,多个不相关的组件需要共享同一份数据时(比如用户登录状态、购物车内容、主题设置),通过props一层层传递就变得很笨重。这时候就需要专门的状态管理方案。

React生态中主流的状态管理工具有:

工具特点适用场景
React ContextReact内置,无需额外安装轻量级全局状态(主题、语言等)
Redux最老牌,生态最成熟,规范严格大型应用、复杂状态逻辑
Zustand极简API,几乎零模板代码中小型应用、追求开发效率
Jotai原子化状态管理,按需更新对性能敏感的精细化场景

不用急着选,等你对React足够熟悉后,自然会知道什么场景用什么工具。在学习阶段,React自带的useStateuseContext就足够应对大部分需求了。

6.3 UI组件库:站在巨人的肩膀上

自己从头写每一个按钮、表单、弹窗、表格?没有必要。React社区有大量高质量的UI组件库,拿来即用:

  • Ant Design(antd):蚂蚁集团出品,国内最流行的企业级组件库
  • Material UI(MUI):基于Google Material Design规范
  • Shadcn/UI:高度可定制、基于Tailwind CSS的组件集合
  • Chakra UI:注重开发体验和可访问性

这些组件库提供了几十甚至上百个开箱即用的组件,从按钮、输入框到复杂的数据表格、日期选择器,帮你节省大量的开发时间。

6.4 服务端渲染与全栈框架

React默认是在浏览器端渲染的——浏览器下载JavaScript文件,执行后生成页面内容。这种方式叫客户端渲染(CSR)。它有两个问题:首屏加载慢(用户要等JavaScript下载和执行完才看到内容),以及搜索引擎优化(SEO)不友好。

为了解决这些问题,React生态发展出了**服务端渲染(SSR)**方案——在服务器上先把React组件渲染成HTML字符串发送给浏览器,浏览器直接显示内容,然后再加载JavaScript让页面变成可交互的。

目前最主流的React全栈框架是Next.js,它把路由、SSR、API路由、静态生成等能力整合在一起,是构建生产级React应用的首选方案。

6.5 CSS方案:样式也百花齐放

在React中给组件添加样式的方式也有很多种:

  • CSS Modules:给每个组件的CSS加上唯一前缀,避免样式冲突
  • Tailwind CSS:在JSX中直接用预定义的工具类写样式
  • styled-components / Emotion:在JavaScript中写CSS(CSS-in-JS方案)

每种方案都有各自的拥护者,没有绝对的好坏之分。后续章节我们会逐步接触这些方案。

6.6 生态全景图

把上面的内容整理成一张图:

                          React 核心

        ┌───────────┬───────┼───────┬───────────┐
        │           │       │       │           │
      路由        状态管理  样式方案  UI组件库    全栈框架
  React Router   Redux    CSS      Ant Design   Next.js
                 Zustand  Modules  MUI          Remix
                 Jotai    Tailwind Shadcn/UI
                          CSS-in-JS

看起来东西很多?不用怕。React的学习路径其实很清晰:先把React核心吃透,再按需学习生态中的工具。 就像学开车,先学好油门刹车方向盘,拿到驾照之后再去了解各种车型和导航系统。


七、为什么选择React——用数据说话

最后,让我们用一些客观事实来回答”为什么全世界都在用React”这个问题:

社区规模:React在GitHub上拥有超过24万颗星,npm周下载量近亿次,在前端框架中遥遥领先。

就业市场:在各大招聘平台上搜索前端相关的职位,要求掌握React的占比常年排名前列。无论国内还是海外,React都是最硬的前端技能之一。

企业采用:Facebook(Meta)、Netflix、Airbnb、Discord、Notion、Shopify……这些你每天可能都在用的产品,背后都有React的身影。

生态成熟度:不管你遇到什么需求——图表、地图、富文本编辑器、拖拽排序、虚拟滚动、PDF生成——几乎都能找到现成的React库。你很少需要”从零造轮子”。

持续进化:React团队一直在推动前端技术的边界。从Hooks到Server Components,React的每一次重大更新都深刻影响了整个前端行业。即使不使用React,其他框架也在借鉴React的思想。

当然,React不是唯一的选择。Vue.js以更低的入门门槛和更直观的模板语法在国内拥有大量用户;Angular以全面的开箱即用能力受到企业级项目的青睐;Svelte以极小的运行时开销代表着前端框架的未来方向。每种框架都有其适用场景。

但如果你要选一个投入时间学习、能最大化职业回报的前端框架,React无疑是最安全的选择之一。


📝 掌握度自测

  1. React和jQuery在构建UI时的核心区别是什么?分别代表了什么编程范式?
  2. React的三大设计哲学是什么?请分别用一句话解释每一个。
  3. 虚拟DOM的工作流程是怎样的?它解决了什么问题?
  4. 请写出一个React函数组件,接收一个name属性(prop),显示”你好,{name}!”。
  5. React中的”单向数据流”是什么意思?它为什么比双向数据流更容易调试?

💡 自我评估

  • 全部答对:基础概念扎实,已经准备好进入下一章学习JSX和组件了!
  • 答对3-4题:理解不错,建议回顾一下薄弱的知识点再继续。
  • 答对1-2题:建议重新阅读本章,尤其要动手运行代码示例。看懂和写出来之间还有一段距离,打开编辑器跟着练一遍吧!

购买课程解锁全部内容

从组件到架构:12 章系统掌握现代 React

¥29.90