React关键概念——是什么,为什么用它

163 阅读24分钟

引言

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也很快会遇到瓶颈
代码量膨胀,维护难度大大增加。


在上面的例子中,代码是命令式编写的,也就是说:

你需要一步步清晰地告诉计算机要干什么、怎么干
如果用更接地气的话描述,这段代码可以拆解成以下操作指令:

  1. 查找网页上第一个button元素,获取它的引用。
  2. 创建一个常量buttonElement,保存这个引用。
  3. 同样地,查找第一个p元素,获取段落的引用。
  4. 创建一个常量paragraphElement,保存这个引用。
  5. buttonElement添加一个点击事件监听器,点击时触发updateTextHandler函数。
  6. 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将变得多么痛苦。

image.png

这个示例中的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找到:

image.png

尽管使用了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本身的核心逻辑分为两个主要包:

  1. react
  2. 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)的开发,主要有两种常见的使用方式:

  1. 管理网页中的某一部分内容(比如页面左下角的聊天窗口)
  2. 管理整个页面以及页面上发生的所有用户交互

这两种做法都是可行的,
更流行、更常见的做法是第二种

用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.jsonpackage-lock.json

    • 管理项目所依赖的第三方包,比如:

      • 生产环境依赖(如reactreact-dom
      • 开发环境依赖(如eslint,用于代码质量检查)
  • .gitignore 等配置文件

    • (用于Git版本管理时忽略某些文件)
  • node_modules/ 文件夹:

    • 存放所有安装的依赖包的实际代码

注意:

  • App.jsxmain.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是一个库,但实际上它由两个主要包组成:reactreact-dom

  • 虽然不使用React,单纯用原生JavaScript也可以构建复杂的UI,
    但那样做往往非常繁琐、容易出错且难以维护

  • React通过声明式(Declarative)方式
    极大地简化了复杂UI的创建过程。

    你只需要定义最终希望呈现的UI状态,
    剩下的DOM操作细节交由React自动完成。

  • 声明式开发意味着:

    你定义目标UI的内容、结构,以及不同状态(比如“模态框是打开还是关闭”),
    由React负责推导出正确的DOM操作

  • react包负责推导UI状态,并管理一个虚拟DOM(Virtual DOM)
    react-domreact-native这样的“桥接包”
    负责把虚拟DOM转换成实际的UI操作指令

  • 使用React可以构建单页应用(SPA)

    由React统一控制所有页面上的UI,以及页面之间的路由跳转。

  • 同时,也可以结合像Next.js这样的框架,

    构建全栈(Full-stack)Web应用,实现前端和后端代码的连接与整合。

  • 使用Vite可以快速创建React项目,

    Vite会自动生成配置好的项目结构,并提供本地实时预览的开发服务器,
    大大提升开发体验。