1.1 React 简介 本节摘要:React 是一个用于构建用户界面的 JavaScript 库,核心思想是组件化、声明式与单向数据流。本节先回答「React 解决了什么」——它把 UI 拆成独立可复用的组件,用声明式描述界面而非命令式操作 DOM;再带你写一个最小的 React 应用,跑通从组件到页面的完整链路。 学习目标 阅读完本节,你应当能够: 说出 React 的三大思想:组件化、声明式、单向数据流 解释虚拟 DOM 与真实 DOM 的关系 手写一个最小 React 应用,说明它的结构 对比函数组件与类组件的基本差异 一、问题与直觉:为什么前端需要 React 先抛一个问题:2009 年前后,前端是怎么写界面的? 那时候 jQuery 是主角。
本节摘要:React 是一个用于构建用户界面的 JavaScript 库,核心思想是组件化、声明式与单向数据流。本节先回答「React 解决了什么」——它把 UI 拆成独立可复用的组件,用声明式描述界面而非命令式操作 DOM;再带你写一个最小的 React 应用,跑通从组件到页面的完整链路。
阅读完本节,你应当能够:
先抛一个问题:2009 年前后,前端是怎么写界面的?
那时候 jQuery 是主角。你想让页面里一个数字跟着按钮点击变,得这样写:先找到这个元素,再改它的文本,再绑定事件。界面每变一次,你就得手动操作一次 DOM。页面小还好,页面一大,「找元素—改内容—绑事件」这套流程堆成山,代码越写越像一团揉皱的线。
React 换了个思路:别告诉我怎么改界面,告诉我界面应该长什么样。
这就是声明式。你描述「当计数为 5 时页面显示什么」,React 负责算出怎么从当前状态变成那个样子,并把差异最小化地应用到真实 DOM 上。开发者从「操作 DOM」的泥潭里解放出来,专注写业务逻辑。
SOURCE 原文给了一个关键结论:React 由 Facebook 开发并维护,被广泛用于构建单页应用(SPA)和移动应用。SPA 的特点是一整张页面由一个入口加载,之后的页面切换不再整页刷新——这正好依赖 React「数据变了,界面跟着变」的能力。
React 应用由一个个独立、可复用的组件构成。每个组件负责渲染页面的一部分,并管理自己的状态。组件可以嵌套:一个页面组件由导航栏组件、内容区组件、底部组件拼起来,每个组件内部还能继续拆分。
组件化的直接收益是复用和可维护。同样的按钮写一次,全站复用;改样式只动一处。SOURCE 原文把组件定义为「React 应用的基本构建块」,这个定义值得记住——后面所有章节都在围绕怎么把组件写得更好。
命令式代码回答「怎么做」,声明式代码回答「是什么」。看一段对比就能体会:
// 命令式思路(伪代码):手动找元素、改内容 const counterEl = document.getElementById('counter'); counterEl.textContent = String(count); // 声明式思路:描述 UI 应该长什么样 function Counter({ count }) { return <p id="counter">{count}</p>; }
React 拿到组件返回的描述(JSX),自己决定怎么更新真实 DOM。开发者不再操心「先删掉旧节点还是先插入新节点」这种细节。
数据从父组件流向子组件,子组件不能直接改父组件的状态。这个「只能往下流」的限制初看是束缚,实际是定心丸——状态变化有明确路径可循,出了问题能顺藤摸瓜找到源头。后面讲状态管理时,单向数据流是贯穿始终的原则。
虚拟 DOM 是真实 DOM 的轻量级表示。当组件状态变化时,React 先在内存里更新虚拟 DOM,再对比新旧虚拟 DOM 找出差异,只把差异部分应用到真实 DOM。
为什么要多这一层?因为直接操作真实 DOM 代价高——浏览器每改一次布局就可能重排重绘。在内存里比较 JavaScript 对象又快又省,算出差量再一次性落地。
概念讲再多,不如敲一个。假设环境已装好(第 1.2 节详述),创建一个最小应用:
function App() { return ( <div className="App"> <h1>Hello, React!</h1> </div> ); } export default App;
这段代码的骨架值得逐行看:
| 代码片段 | 作用 |
|---|---|
function App() |
定义一个名为 App 的函数组件 |
return ( ... ) |
函数组件必须返回一个 JSX 元素,描述它的 UI |
<div className="App"> |
JSX 元素,和 HTML 相似但 class 写成 className |
export default App |
导出组件,供其他文件引用 |
把 App 挂载到页面靠入口文件:创建 root 并调用 render,把组件渲染到指定的真实 DOM 节点上。SOURCE 原文里这个流程清晰标注了「import React → 定义组件 → 导出」,这就是 React 应用的最小结构。
React 组件可以是函数,也可以是类。函数组件就是一个接收 props、返回 JSX 的纯函数:
function Greeting(props) { return <h1>Hello, {props.name}!</h1>; }
类组件是 ES6 类,继承自 React.Component,必须实现 render 方法:
import React from 'react'; class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } increment = () => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <div> <p>Count: {this.state.count}</p> <button onClick={this.increment}>Increment</button> </div> ); } }
两者的定位在 React 16.8 之后发生了变化:Hooks 让函数组件也能管理状态,官方推荐新代码用函数组件。但类组件的生命周期方法、this 绑定问题,仍然大量存在于存量项目里,读旧代码、维护老项目时你必须认识它。
这是最容易搞混的一对概念,一次讲清楚:
看个例子。父组件把用户信息作为 props 传给子组件,子组件展示它:
function Profile(props) { return ( <div> <h2>{props.name}</h2> <p>Age: {props.age}</p> </div> ); } function App() { return <Profile name="John Doe" age={30} />; }
name="John Doe" 和 age={30} 是父组件传给 Profile 的 props。注意 age 用了花括号——JSX 里凡是要传 JavaScript 值(数字、变量、表达式)都要包花括号,字符串字面量可以直接写。
state 的用法上面类组件 Counter 里已经见过:constructor 里初始化,setState 里修改。函数组件用 useState(第 2.2 节详解)。
💡 关键直觉:把组件想象成纯函数——同一次渲染里,props 和 state 决定了输出什么界面。data 变,渲染变;渲染变,用户看到的内容变。抓住这条因果链,后面所有内容都是它的展开。
React 的诞生和 Facebook 的痛点绑在一起。2011 年前后,Facebook 的广告系统 UI 复杂度爆炸,传统 DOM 操作式的开发已经撑不住——界面状态一多,手动同步 DOM 的代码量呈指数增长,bug 层出不穷。Facebook 工程师 Jordan Walke 带着团队做出了一个叫 FaxJS 的原型,核心思路就是「用函数描述 UI,数据变了自动更新」,后来这个思路演化成 React。
2013 年 React 开源,2015 年 React Native 发布,同一套声明式组件模型被带到了移动端——这也是 React 后来能横跨 Web 与 App 的起点。2016 年 Redux 出现,把单向数据流和不可变状态推到了极致,成为 React 生态事实上的标配一段时间。2017 年 React 16 引入 Fiber 架构重构了内部调度,让渲染可以被中断、被分片,为 Suspense 和并发渲染铺路。2019 年 React 16.8 正式发布 Hooks,函数组件从此能管理状态和副作用,React 的写法发生了根本变化。
这条时间线告诉我们一件事:React 的每次大版本都在解决「声明式 UI 在更大规模下如何保持高效」这个命题。理解了这一点,你就明白为什么虚拟 DOM 要做 Diff、为什么要有 key、为什么 Hooks 的依赖数组那么讲究——它们不是炫技,是同一个问题在不同阶段的解法。
经常有新手问:学 React 是不是还得学一堆别的?答案是:React 本身只管视图,但一个能上线的项目确实需要配套工具。大致分五类:
| 类别 | 常见选择 | 何时需要 |
|---|---|---|
| 脚手架 | Create React App、Vite | 建项目时 |
| 路由 | React Router | 多页面时 |
| 状态管理 | Context、Zustand、Redux Toolkit | 跨组件共享状态时 |
| 样式 | CSS Modules、styled-components、Tailwind | 写样式时 |
| 测试 | Jest、React Testing Library | 保证质量时 |
这份清单先混个眼熟即可。第 4 章会逐个展开,并给出选型依据——哪类项目用哪套组合,为什么要这样选,代价是什么。
SOURCE 原文列了几个选 React 的理由,我按实际分量重新排一下:
| 理由 | 说明 | 分量 |
|---|---|---|
| 组件化 | 代码可维护、可复用 | 核心优势,Vue 也有 |
| 声明式 | 专注于「界面长什么样」而非「怎么改 DOM」 | 与 Vue 相似,强于原生 |
| 生态庞大 | 组件库、状态库、脚手架选择极多 | React 的护城河 |
| 跨平台 | 配 React Native 可写移动应用 | 独有优势之一 |
| 虚拟 DOM | 减少真实 DOM 操作,提升性能 | 现在各家都有,已不稀奇 |
一个诚实的判断:单论「声明式 + 组件化」,React 和 Vue 是一类东西。React 真正的护城河是生态——前后端、工具链、招聘市场几乎被它占满。选 React 很少是纯技术决定,更多是「跟谁一起写、招什么人、生态够不够」的综合判断。
⚠️ 常见坑:以为 React 是框架。它是库——只管视图层,路由要自己接 React Router,状态管理要自己选 Redux 或 Zustand,脚手架要自己配 Vite 或 CRA。这既是自由也是责任,第 3、4 章专门处理这些选型问题。
有人觉得「声明式 = 不用管 DOM 操作」,这句话只对了一半。React 只是替你管了,不代表你不用懂。你需要知道虚拟 DOM 是什么、Diff 怎么比、为什么 key 影响性能——因为声明式只是把操作藏进了框架,性能瓶颈依然会找上门。这也是为什么第 2 章第一节就讲虚拟 DOM 与 Diff:它不是选学内容,是理解 React 一切行为的钥匙。
还有一层理解:声明式并不是 React 独有。HTML 本身就是声明式的——你写一个列表标签描述列表,浏览器负责渲染。React 的贡献是把这种声明式能力推进到「数据变化时界面自动跟随」的层面。想明白这层对比,你就理解了 React 在技术史上做的事:它把「界面等于状态的函数」这个函数式思想从数学书搬进了浏览器。
单页应用(SPA)的核心诉求是:切换页面不整页刷新。传统多页应用每次跳转都向服务器要一个全新的页面,白屏、闪烁、状态丢失都难以避免。SPA 只加载一次,之后的路由切换在前端完成——但前端怎么知道界面该变成什么?答案还是那句:界面是状态的函数。路由变了,状态变了,组件重新渲染,页面局部更新。
React Native 更进一步:同样的组件模型,编译成原生控件渲染,Web 与移动端共享大部分业务逻辑。SOURCE 原文特别强调 React 可与 React Native 结合构建跨平台应用,这个「一份心智、两处落地」的能力是不少团队押注 React 的原因。当然它不是免费的——跨平台带来的是抽象层的取舍,性能损耗和平台特性缺失总要还一点,这属于后话,本章先知道有这条路即可。
下一节我们搭建真正的开发环境——光会看代码不够,得让代码跑起来,先比较 CRA 和 Vite 两条路怎么选。环境跑通之后,你就有条件亲手验证本节讲的所有概念了。