引言
React.js(或者简称为React,本文也将主要使用这个简称)是目前最受欢迎的前端JavaScript库之一——根据2023年Stack Overflow开发者调查,它甚至可能是最受欢迎的前端库。根据统计,React已经被全球前1000个网站中超过5%的网站使用。与其他流行的前端JavaScript库和框架(如Angular)相比,在关键指标(比如通过npm每周下载次数)上,React遥遥领先。npm是一个常用的JavaScript包管理和下载工具。
虽然不了解React的内部机制和使用原因也可以写出不错的React代码,但如果能理解你正在使用的工具以及选择它的理由,你会更快掌握高级概念,并在开发过程中更少出错。
因此,在学习React的核心概念、示例代码之前,首先需要搞清楚:React到底是什么?为什么会有React?
了解这些基础背景,有助于你更好地掌握React的内部运作原理,以及它为何设计出如今的特性。
如果你已经非常清楚自己为什么用React、为什么行业内普遍采用类似React这样的方案而不是直接使用原生JavaScript(即不依赖任何框架或库的纯JavaScript,后面章节会详细说明),并且理解了React的设计理念和语法,那么你可以直接跳过本章,进入后面更偏实践的内容。
但如果你只是以为自己懂了,却并不百分百确认,那我建议你认真读完这一章。
什么是React?
React是一个JavaScript库。
如果你打开它的官方网站(地址:react.dev/ ),可以看到官方是这样描述它的:
"用于构建网页和原生用户界面的库(The library for web and native user interfaces)"
那么,这句话具体是什么意思呢?
首先要理解,React本质上是一个JavaScript库。作为本书的读者,你应该已经了解什么是JavaScript,以及为什么要在浏览器中使用它。
JavaScript可以让网页变得有交互性,用户触发事件后,你可以通过JavaScript动态修改页面内容。这一点非常重要,因为它让我们能够打造高度互动的网页用户界面(UI) 。
但**“库”到底是什么**?React又是怎么帮助我们构建UI的呢?
虽然“库”和“框架”之间有哲学式的争论,但从实用角度来看,库就是:
一组可以直接调用的功能集合,它帮助你用更少的代码、更少的出错风险,完成原本需要自己大量手动编写的复杂功能。
简单说,库让开发更高效、代码更简洁、也更安全。
React就是这样一个库,专注于帮你构建交互式和响应式的用户界面。
不仅如此,React不仅能用来构建网页界面(即浏览器加载的网站),还可以配合React Native开发原生的移动应用。React Native在内部也使用了React的核心概念。因此,本书介绍的React理念,不管你未来是做网页、还是做App开发,都是通用的。
不过,为了聚焦讲解,后续示例都会围绕基于浏览器的React开发进行。
无论目标平台是Web还是移动端,仅用原生JavaScript来开发复杂的交互界面,都很容易变得又臃肿又混乱。React的出现,就是为了解决这个问题。
“原生JavaScript”的问题
在网页开发中,Vanilla JavaScript通常指的是不依赖任何框架或库的纯JavaScript。
也就是说,所有的功能都需要你自己用原生JavaScript从零开始实现,不能借助像React、Angular这样的框架或库提供的各种便捷功能。
使用原生JavaScript的一个明显好处是:网页访问者需要下载的JavaScript代码更少。因为大型框架和库通常体积不小,很容易额外增加50KB甚至更多的下载量。
但缺点也很明显:
作为开发者,你必须亲自实现所有功能,这不仅容易出错,还极其耗费时间。
因此,尤其是当网站UI越来越复杂时,单靠原生JavaScript开发和维护就会变得非常困难。
React通过**从命令式(Imperative)转向声明式(Declarative)**的方法,简化了这种UI的创建与管理。
虽然这样一句话听起来很美,但如果你以前没用过React或者类似的框架,可能一时很难真正理解。
为了弄清楚“命令式和声明式”的区别,以及为什么要用React而不是直接用原生JavaScript,我们不妨先回头看看原生JavaScript是怎么工作的。
来看一个简单的例子,用原生JavaScript实现以下操作:
- 给一个按钮添加点击事件监听器。
- 当按钮被点击时,修改一个段落文本的内容。
示例代码如下:
const buttonElement = document.querySelector('button');
const paragraphElement = document.querySelector('p');
function updateTextHandler() {
paragraphElement.textContent = 'Text was changed!';
}
buttonElement.addEventListener('click', updateTextHandler);
这个例子故意保持得很简单,所以看起来并不复杂或难以理解。
它只是展示了用原生JavaScript处理UI交互的一种基本方式(后面我们还会看到更复杂的例子)。
不过,即使是这么直白的例子,一旦UI变得复杂、需要处理更多交互时,原生JavaScript也很快会遇到瓶颈。
代码量膨胀,维护难度大大增加。
在上面的例子中,代码是命令式编写的,也就是说:
你需要一步步清晰地告诉计算机要干什么、怎么干。
如果用更接地气的话描述,这段代码可以拆解成以下操作指令:
- 查找网页上第一个
button元素,获取它的引用。 - 创建一个常量
buttonElement,保存这个引用。 - 同样地,查找第一个
p元素,获取段落的引用。 - 创建一个常量
paragraphElement,保存这个引用。 - 给
buttonElement添加一个点击事件监听器,点击时触发updateTextHandler函数。 - 在
updateTextHandler函数中,把paragraphElement的文本内容设置为“Text was changed!”。
可以看到,每一个操作步骤都必须明确地在代码里写出来。
这并不意外——毕竟,大多数编程语言都是这么运作的:
定义一系列顺序执行的操作指令。
这种做法很合理,因为程序执行的顺序必须可控、可预期。
但是,一旦涉及到复杂的网页UI,这种命令式编程方式就会变得非常笨重。
作为开发者,你不得不写下大量的指令,很多指令虽然细碎、价值感不强,但又不能省略。
比如:
- DOM查询
- 元素插入
- 元素修改
- 事件监听
- UI状态管理
你的核心业务逻辑(比如,点击按钮后要显示什么文本)其实只占了代码的一小部分,
而大部分代码都是在操作DOM和处理UI细节。
最终结果就是,你既要描述:
- 如何与页面元素交互(技术细节)
- 又要描述交互后希望页面呈现的最终状态(业务需求)
这让开发变得极其繁琐。
📢 小提示:本书默认你已经了解DOM是什么。
简单来说,DOM(Document Object Model)是JavaScript与网页HTML之间沟通的桥梁。
通过浏览器内置的DOM API,JavaScript可以读取、创建、修改、删除网页元素及其内容。
如果你想了解更多,可以参考这个文章:academind.com/tutorials/w…
在现代网页开发中,用户界面(UI)常常非常复杂,
比如:
- 监听用户输入
- 把输入数据发送到服务器验证
- 把验证反馈显示到页面上
- 如果提交了错误数据,弹出错误提示模态框
像前面“点击按钮改文本”这种简单交互当然还好,但如果用原生JavaScript去构建复杂UI,代码会迅速变得臃肿混乱。
大量的DOM查询、操作、事件监听,代码层层叠叠,而且还要保证随时正确更新页面内容,避免出错,
这会让开发者疲于奔命。
(这里书中提到了截图示例,不过完整可运行的代码在GitHub上可以找到:查看示例代码)
如果你看一下这些示例代码,你就能想象:
随着功能越来越多,原生JavaScript管理UI将变得多么痛苦。
这个示例中的JavaScript文件已经有大约110行代码了。
即使经过代码压缩("压缩"指的是自动缩短代码,比如用短变量名替换长变量名、去除多余空格等,这里使用的是:www.toptal.com/developers/…
然后再重新换行统计代码行数,依然有大约80行。
这意味着,仅仅为了实现一个带有基础功能的简单UI,就需要写满满80行代码。
而实际上,真正的业务逻辑(比如输入校验、判断何时显示弹窗、设置输出文本等)只占了整个代码量的一小部分——在这个例子中,大概只有20到30行(压缩后大约20行左右)。
也就是说,大约75%的代码,都花在了纯粹的DOM操作、DOM状态管理和其他类似的“模板化任务”上。
通过这些例子和数据可以看出:
要手动控制所有UI元素及其各种状态(比如某个信息提示框是显示还是隐藏)是一件极具挑战性的事情。
如果单靠原生JavaScript去实现这些界面,很容易导致代码又复杂又容易出错。
这也是为什么命令式编程方法(一步步地详细描述每个操作)在这种情况下遇到了极限。
而这,正是React出现并提供声明式开发方式的原因。
📢 注意:
这里并不是一篇科学论文,上述示例也不是严格意义上的科学研究。
不同的人在统计代码行数或者在划分什么算是“核心业务逻辑”时,结果可能会有所不同。
但核心结论不会改变:大量代码(在本例中是大部分)都在处理DOM操作,而不是在描述网站真正的功能逻辑。
React与声明式代码
回到之前那个简单的示例代码,这里是用React重写后的版本:
import { useState } from 'react';
function App() {
const [outputText, setOutputText] = useState('Initial text');
function updateTextHandler() {
setOutputText('Text was changed!');
}
return (
<>
<button onClick={updateTextHandler}>
Click to change text
</button>
<p>{outputText}</p>
</>
);
}
这个小程序实现了跟之前原生JavaScript示例相同的功能:
- 给按钮添加点击事件监听(这里使用了React特有的语法:
onClick={...}) - 点击按钮后,修改段落文字为新的文本
不过,这段代码看起来却完全不一样——
它像是JavaScript和HTML的混合体。
实际上,React使用了一种叫做JSX的语法扩展(本质上是:在JavaScript中嵌入XML风格的标记)。
这里你只需要知道,JSX之所以能够正常工作,是因为在每个React项目的构建流程中,都会有一个预处理(或转译)步骤。
所谓预处理,就是使用项目内置的工具,在代码部署前,先分析并转换代码。
因此,开发阶段可以使用像JSX这样的特殊语法,虽然浏览器本身并不认识JSX,但它会在发布前被转换为标准的JavaScript。
(关于JSX的详细介绍将在第2章《理解React组件与JSX》中展开。)
另外,这段示例代码还用到了React的一个重要特性:State(状态) 。
关于State,后面会在第4章《事件与状态管理》详细讲解。
这里你可以简单理解为:State就是一个特殊的变量,一旦它的值改变,React就会自动更新页面UI。
你在这个例子里看到的,正是React的声明式开发方式:
- 你编写JavaScript逻辑(比如某个函数)
- 同时,你定义哪些HTML元素应该受这些逻辑影响
你不需要像原生JavaScript那样:
- 明确地选择某个DOM元素
- 再一步步指令式地修改它
相反,使用React和JSX时,你只需要专注于业务逻辑和最终希望呈现的HTML结构。
这些HTML结构中,通常包含了一些动态的值,这些值是由你的JavaScript逻辑计算得出的。
在上面的例子中:
outputText是由React管理的状态。- 当按钮被点击时,
updateTextHandler函数被触发, - 调用
setOutputText来更新状态为新的字符串'Text was changed!'。 - 在JSX中,
<p>{outputText}</p>引用了这个状态值。 - 因此,每次
outputText发生变化,React就会自动找到对应的段落元素,并更新它的内容。
这,就是声明式开发在实际中的体现:
作为开发者,你不需要关心怎么找到段落元素、怎么修改它的内容这些技术细节。
你的任务只是告诉React:“在这个地方,展示outputText的当前值”,剩下的都交给React去处理。
值得注意的是:
-
这段React代码并没有比原生JavaScript代码更短,甚至稍微更长一些。
-
之所以这样,是因为这里的例子被故意简化了,只演示了最基础的功能。
-
如果你的整个项目真的只有这点内容,确实没必要上React,反而增加了点小负担。
-
但! 如果你想起之前那个更复杂的原生JavaScript示例(涉及更多交互和UI状态管理),
再和用React重写的版本对比,就能明显感受到:随着复杂度提升,React能极大简化开发难度和维护成本。
📢 小提示:
示例代码可以在GitHub找到:
尽管使用了React,这段代码看起来依然不算短,主要原因是所有的JSX代码(即HTML输出部分)都包含在JavaScript文件中。
如果你忽略截图中右半部分几乎所有的内容(因为原生JavaScript文件本来也没有包含HTML),
那么React代码实际上就精炼得多了。
但最重要的是,如果你仔细观察这段React代码(包括之前那个简短的例子),你会发现:
完全没有任何选取DOM元素、创建/插入DOM元素、或直接修改DOM元素的操作。
这,正是React的核心理念:
你不再需要像原生JavaScript那样,详细写出每一个操作步骤和指令,
而是专注于"大局"——页面内容想要达到的最终状态。
使用React,你可以把JavaScript逻辑和界面(markup)代码自然融合在一起,
无需再处理低层次的DOM操作细节,比如document.getElementById()这类繁琐的选择器调用。
采用这种声明式(Declarative)开发方式,取代原生JavaScript的命令式(Imperative)开发方式,
能够让开发者专注于业务逻辑和HTML内容的各种状态变化。
你不需要再写出每个具体步骤(比如“添加事件监听器”“选中段落元素”等等),
这极大简化了复杂UI的开发过程。
📢 小提示:
需要特别强调的是:
如果你的项目UI非常简单,仅仅几行原生JavaScript就能搞定,没必要特意引入React。
在这种情况下,使用原生JavaScript会更直接高效。
第一次看到React代码时,
你可能会觉得它非常陌生甚至奇怪——
它不是你习惯的那种传统JavaScript风格。
但请记住,React代码本质上仍然是JavaScript,
只不过:
- 增加了JSX特性
- 引入了State等React特有的功能
如果你换个角度想一想,会更容易理解:
👉 通常,我们用HTML来定义网页UI(页面内容及结构)。
在写HTML时,你也不是逐步下命令的,而是通过一层层嵌套的标签,
表达内容的结构、层级和含义。
从这个角度看,传统的原生JavaScript开发方式反而显得有点奇怪了:
为什么在HTML里可以直接声明结构,而到了用JavaScript操作UI时,却要“低层次地”逐条写指令?
(比如:"在这个按钮下面插入一个段落元素,并设置文本为某某")
React最终就是把这种HTML式的表达方式带回来了:
让你以一种更自然、直观的方式,
同时写出动态JavaScript逻辑和与之关联的界面结构(HTML) 。
这样,动态逻辑和界面布局可以并肩而写,
开发体验更加清晰、简洁、高效。
React是如何操作DOM的
如前所述,编写React代码时,你通常会像之前展示的那样,
使用JSX语法扩展,把HTML结构和JavaScript逻辑融合在一起。
需要特别指出的是:
JSX代码本身是不能直接在浏览器中运行的。
在部署之前,JSX必须经过预处理(Pre-processing),
转换成普通的JavaScript代码,浏览器才能识别和执行。
关于JSX的详细转换过程,我们将在下一章深入探讨。
这里你只需要记住:
- JSX在正式运行前,必须被转译成标准JavaScript。
不过,即使转译完成后,生成的JavaScript代码依然不会直接包含任何原生DOM操作指令。
转译后的代码,会调用内置在React库中的各种工具方法和函数,
这些方法由React官方维护,封装在React的核心包中。
在内部,React会创建一个“虚拟DOM树”(Virtual DOM) ,
这个结构用来反映UI当前的状态。
(第10章《React幕后机制与优化机会》会详细讲解虚拟DOM的原理和优化方法。)
这也是为什么,React本身的核心逻辑分为两个主要包:
react包react-dom包
具体来说:
react包:
是第三方的JavaScript库,必须引入到项目中,才能使用React的基本功能(比如JSX、State等)。
它主要负责创建虚拟DOM,并管理当前UI状态的变化。react-dom包(尤其是其中的react-dom/client部分):
则充当一个**“翻译桥梁” ,
负责把React代码和内部生成的虚拟DOM**,
翻译成浏览器可以识别的真实DOM操作指令。
也就是说,真正去选取、更新、删除、创建DOM元素的,是react-dom这个包。
之所以要这样分开设计,是因为:
👉 React不仅可以用来构建网页,也可以构建其他类型的界面。
比如:
- React Native:
允许开发者用React语法来开发原生移动应用。
在React Native项目中,你同样需要引入react包,
但不会用到react-dom,而是改用react-native包,
用来生成iOS/Android平台下对应的界面元素。
在本书中, “React”泛指react包以及它的桥接包(比如react-dom) 。
📢 小提示:
如前所述,本书的核心聚焦在React本身。
所以,讲解的概念适用于网页(浏览器环境) ,也适用于移动端(React Native环境) 。
不过,为了避免增加额外的复杂性,
本书所有示例将专注在网页端(即使用react-dom)。
初识SPA(单页应用)
React可以用来简化复杂用户界面(UI)的开发,主要有两种常见的使用方式:
- 管理网页中的某一部分内容(比如页面左下角的聊天窗口)
- 管理整个页面以及页面上发生的所有用户交互
这两种做法都是可行的,
但更流行、更常见的做法是第二种:
用React管理整个网页,而不仅仅是某一部分。
为什么?
因为大多数拥有复杂UI的网站,通常不仅有一个复杂元素,
而是页面中到处都有各种复杂组件。
如果只在部分区域使用React,其他区域还用传统方式,反而会增加整体开发和维护的复杂度。
因此,用React管理整个网站已经成为非常普遍的选择。
而且,使用React并不限于单一页面。
实际上,React还可以用于处理URL路径变化,
当用户切换页面时,
动态更新需要变化的部分内容,以呈现新的页面效果。
这种功能被称为路由(Routing) 。
为了实现路由功能,通常会使用像react-router-dom这样的第三方包(具体将在第13章《使用React Router构建多页面应用》详细介绍)。
通过整合React和路由功能,
你可以创建一个由React全面控制界面的完整网站。
这种开发模式有一个专门的名字,叫做SPA(Single Page Application,单页应用) 。
在SPA中,网站通常只有一个HTML文件(通常叫做index.html),
这个HTML文件的唯一作用就是加载React JavaScript代码。
之后,
React库和你的React代码接管了整个页面的控制权,
整个网站的UI都是由JavaScript(通过React)动态创建和管理的。
不过,现在越来越流行的一种趋势是:
构建全栈(Fullstack)React应用,
即前端和后端代码整合在一起开发。
像Next.js这样的现代React框架,大大简化了全栈应用的开发流程。
虽然无论开发哪种类型的应用,
React的核心概念都是相同的,
但本书将在后续章节中更深入地探索全栈React应用的开发,
具体包括:
- 第15章《服务端渲染与使用Next.js构建全栈应用》
- 第16章《React服务器组件与服务器端操作(Server Actions)》
- 第17章《理解React Suspense与use()钩子》
总结来说,
本书将为你打下坚实基础,
无论是开发传统的React前端项目,还是开发更高级的全栈项目,
核心的构建块和关键概念始终如一。
使用 Vite 创建 React 项目
要开始使用React,第一步就是创建一个React项目。
虽然官方文档推荐使用像 Next.js 这样的框架,
但对于刚开始学习React、了解React概念的人来说,这种做法反而容易让人感到负担过重。
因为Next.js和其他类似框架,会引入自己的一套概念和语法,
这样学习React时,很容易混淆React特性和框架特性,
进而让人感到沮丧。
而且,并不是所有React应用都需要做成全栈应用,
所以使用像Next.js这样的重型框架,反而增加了不必要的复杂性。
因此,现在非常流行的一种做法是:
用Vite来搭建React项目。
Vite是一个开源的开发和构建工具,
可以用来创建和运行各种前端项目——
React只是Vite支持的众多选项之一。
使用Vite创建的React项目,自带了:
- 内置且预配置好的构建流程,能自动处理JSX代码的转译
- 本地开发服务器,可以在开发过程中实时预览React应用效果
因为React项目通常会使用JSX,而浏览器无法直接理解JSX,
所以必须有这样的预处理步骤(如前所述)。
如何用 Vite 创建一个React项目
首先,确保你的电脑上已经安装了 Node.js(推荐安装最新版或LTS版本)。
Node.js官方下载地址:nodejs.org/
安装完Node.js后,你就可以使用内置的 npm 命令了,
通过npm来安装并使用Vite创建React项目。
打开你的命令行工具(Windows的cmd、Linux的bash或Mac的terminal),
进入你希望创建新项目的目录(使用 cd 命令切换目录),然后运行:
npm create vite@latest my-react-project
执行后,系统会提示你选择要使用的框架或库,
在选项中选择 React,然后选择 JavaScript。
这个命令会在你当前所在的目录下,
创建一个名为my-react-project的子文件夹,
里面包含了React项目的基础结构(各种必要的文件和文件夹)。
注意:
- 创建项目只是生成了基本的项目骨架,
- 并没有自动安装依赖包(如react、react-dom等) 。
因此,接下来需要进入新创建的项目文件夹:
cd my-react-project
然后安装项目依赖:
npm install
安装成功后,项目就搭建好了。
运行本地开发服务器
要预览你刚创建的React应用,可以运行:
npm run dev
这条命令会启动Vite内置的开发服务器,
在本地开启一个网址(默认是 http://localhost:5173),
并实时托管、构建、预览你的React应用。
在开发过程中,通常会一直让这个服务器开着,
因为:
- 每当你保存代码修改时,网页会自动刷新更新,
- 几乎可以实时预览改动效果。
完成一天的开发后,可以在终端窗口按下 Ctrl + C,停止服务器。
以后想继续开发时,只需要再次运行 npm run dev 启动即可。
📢 小提示:
如果在用Vite创建React项目时遇到问题,
也可以直接下载作者提供的初始项目模板:
github.com/mschwarzmue…下载后,记得在项目目录中运行:
bash 复制编辑 npm install安装依赖包,然后再运行:
bash 复制编辑 npm run dev来启动项目。
Vite创建的React项目的基本结构
一般来说,每个新的Vite-React项目包含以下关键文件和文件夹:
-
src/文件夹:main.jsx:项目的入口脚本文件(最先执行)App.jsx:应用的根组件(后面章节会详细讲解组件)- 各种CSS样式文件(通过JavaScript导入使用)
assets/子文件夹,用来存放图片或其他资源
-
public/文件夹:- 存放网站中需要公开访问的静态资源(比如favicon)
-
index.html:- 整个应用的唯一HTML页面
-
package.json和package-lock.json:-
管理项目所依赖的第三方包,比如:
- 生产环境依赖(如
react、react-dom) - 开发环境依赖(如
eslint,用于代码质量检查)
- 生产环境依赖(如
-
-
.gitignore等配置文件- (用于Git版本管理时忽略某些文件)
-
node_modules/文件夹:- 存放所有安装的依赖包的实际代码
注意:
App.jsx和main.jsx文件使用的是.jsx后缀(不是.js)。- 在Vite项目中,凡是包含JSX语法的文件,都应该用
.jsx作为后缀。
大部分与React相关的逻辑代码,
都会写在 App.jsx 或自定义组件文件中。
(我们将在下一章详细学习组件。)
📢 小提示:
package.json是你手动管理依赖包和版本的地方。package-lock.json是Node.js自动生成的,用来锁定确切的依赖版本,确保安装的一致性。你可以在这里了解更多关于这两个文件的信息:
docs.npmjs.com/
另外,node_modules 文件夹通常不会上传到GitHub,
因为只需要保存package.json就足够了,
其他人拉取代码后,运行npm install就能在本地重新生成node_modules。
总结与关键要点
-
React是一个库,但实际上它由两个主要包组成:
react和react-dom。 -
虽然不使用React,单纯用原生JavaScript也可以构建复杂的UI,
但那样做往往非常繁琐、容易出错且难以维护。 -
React通过声明式(Declarative)方式,
极大地简化了复杂UI的创建过程。你只需要定义最终希望呈现的UI状态,
剩下的DOM操作细节交由React自动完成。 -
声明式开发意味着:
你定义目标UI的内容、结构,以及不同状态(比如“模态框是打开还是关闭”),
而由React负责推导出正确的DOM操作。 -
react包负责推导UI状态,并管理一个虚拟DOM(Virtual DOM) ;
像react-dom或react-native这样的“桥接包” ,
负责把虚拟DOM转换成实际的UI操作指令。 -
使用React可以构建单页应用(SPA) ,
由React统一控制所有页面上的UI,以及页面之间的路由跳转。
-
同时,也可以结合像Next.js这样的框架,
构建全栈(Full-stack)Web应用,实现前端和后端代码的连接与整合。
-
使用Vite可以快速创建React项目,
Vite会自动生成配置好的项目结构,并提供本地实时预览的开发服务器,
大大提升开发体验。