初识React —— 为什么全世界都在用它
2013年,Facebook开源了一个用于构建用户界面的JavaScript库。没有人想到,这个当初被不少人嘲笑”在JavaScript里写HTML太疯狂了”的项目,会在十多年后成为全球最流行的前端框架之一,彻底改变了我们构建Web应用的方式。这个项目就是React。
📋 开篇自测:你已经知道多少?
- 你能说出React和jQuery在构建UI时的根本区别吗?
- 你听说过”虚拟DOM”吗?能用自己的话解释它为什么存在吗?
- 你知道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>
);
}
组件化带来了三个巨大的好处:
- 可复用:写好一个
Button组件,整个应用甚至多个项目都能用 - 可维护:出bug了只需要定位到对应的组件,不用在几千行的文件里大海捞针
- 可协作:团队成员可以各自负责不同的组件,互不干扰
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的轻量级副本。
更新过程是这样的:
- 当数据变化时,React先在内存中用虚拟DOM构建一棵”新树”
- 将”新树”与”旧树”进行对比(这个过程叫Diff)
- 计算出最小的变更集
- 一次性将这些变更应用到真实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页面中id为root的元素,然后把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,描述这个组件的UIexport 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(钩子函数)。它做了三件事:
- 创建一个名为
count的状态变量,初始值为0 - 创建一个名为
setCount的函数,用来更新count的值 - 返回这个数组,我们用解构赋值来接收
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;
看到了吗?Display和ActionButton各自负责一件事情。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 Context | React内置,无需额外安装 | 轻量级全局状态(主题、语言等) |
| Redux | 最老牌,生态最成熟,规范严格 | 大型应用、复杂状态逻辑 |
| Zustand | 极简API,几乎零模板代码 | 中小型应用、追求开发效率 |
| Jotai | 原子化状态管理,按需更新 | 对性能敏感的精细化场景 |
不用急着选,等你对React足够熟悉后,自然会知道什么场景用什么工具。在学习阶段,React自带的useState和useContext就足够应对大部分需求了。
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无疑是最安全的选择之一。
📝 掌握度自测
- React和jQuery在构建UI时的核心区别是什么?分别代表了什么编程范式?
- React的三大设计哲学是什么?请分别用一句话解释每一个。
- 虚拟DOM的工作流程是怎样的?它解决了什么问题?
- 请写出一个React函数组件,接收一个
name属性(prop),显示”你好,{name}!”。 - React中的”单向数据流”是什么意思?它为什么比双向数据流更容易调试?
💡 自我评估
- 全部答对:基础概念扎实,已经准备好进入下一章学习JSX和组件了!
- 答对3-4题:理解不错,建议回顾一下薄弱的知识点再继续。
- 答对1-2题:建议重新阅读本章,尤其要动手运行代码示例。看懂和写出来之间还有一段距离,打开编辑器跟着练一遍吧!
购买课程解锁全部内容
从组件到架构:12 章系统掌握现代 React
¥29.90