React 开发入门

什么是 React ?

在 2024 年的今天,我们的浏览器仍然采用最基本的 html + CSS + JavaScript 构建页面,但是我们可以很明显的感觉到,编写原生三件套代码是比较复杂的。并且在 Web 项目规模庞大和设备多样性越发丰富的今天,代码的可维护性、开发效率、跨平台开发、性能等因素都会影响到我们对开发工具的选择。因此人们想到了一种方式,可不可以先编写一些简化的、高效率的代码,然后用他们生成 html + CSS + JavaScript 并运行在浏览器上。就像是我们不必编写汇编语言,写 C 语言即可构建可执行文件。就这样, React 诞生了

维基百科这样定义 React :React 是一个前端 JavaScript 工具,用于基于 UI 组件构建用户界面

当前常用的工具(库或框架)不止有 React ,比如 Vue、Angular 等都是常见的构建前端页面的工具

MDN 这样描述 React :React 不是一个框架——它的应用甚至不局限于 Web 开发,它可以与其他库一起使用以渲染到特定环境。例如, React Native 可用于构建移动应用程序; React 360 可用于构建虚拟现实应用程序……

React 是一门令人惊叹的前端技术,其创新性的设计理念和强大的功能使其成为现代 Web 开发的领军者。通过引入虚拟 DOM 、组件化开发和单向数据流等概念, React 不仅提高了应用的性能和可维护性,还让开发者在构建复杂用户界面时拥有更高的灵活性和控制力。 JSX 语法的引入使 UI 代码更加直观和易读,而庞大而活跃的社区生态系统则为开发者提供了丰富的工具和支持。同时, React 的跨平台特性使其在移动开发领域也大放异彩,成为 React Native 的基石。 React 可以被视为一个跨时代的前端开发工具和框架。其引入了许多创新性的概念和技术,对前端开发的方式和效率产生了深远的影响

React 的前世今生

诞生背景:

React 由 Facebook 的工程师 Jordan Walke 于 2011 年创建。最初的目标是解决 Facebook 内部复杂应用的性能问题和开发效率问题

开源发布:

React 在 2013 年 5 月正式开源发布。这一举动使得更多的开发者能够使用和贡献 React ,促进了它的快速发展

组件化开发:

React 采用了组件化的开发方式,将 UI 拆分为独立的组件,使得代码更加模块化、可维护。组件化开发成为 React 的核心理念之一,也被其他框架和库广泛采用

JSX 语法:

React 引入了 JSX 语法,允许在 JavaScript 中编写类似 XML 的标记,使 UI 的描述更加直观。 JSX 在编译时会被转换为普通的 JavaScript 代码,使开发者的代码编写更为便捷

React Native:

随着移动端应用的兴起, React 的影响力扩展到了移动开发领域。 React Native 使开发者能够使用 React 的思想和组件化开发方式来构建原生移动应用,实现了跨平台的目标

持续更新:

React 团队持续进行版本迭代和改进,引入新的特性和优化,以适应前端技术的不断发展和用户需求的变化


(上面随便看看就行,下面才是正文)

React ,启动!

在某个你想要建立该项目的目录下,运行 React 官方提供的脚手架:

1
npm create react-app learn-react --template typescript -y

这行命令表示在当前目录下创建一个新的 react 项目,项目名称为 learn-react ,使用 TypeScript 而不是 JavaScript

如果安装顺利,使用命令 cd learn-react 进入项目文件夹,输入 npm start 即可编译整个应用,随后应当就可以通过本地 3000 端口访问到 React 的欢迎界面了

image-20240122153740520

为了更好地阅读本文档,推荐下载简化版 React 框架。其中仅保留了最基础、最必要的代码文件

下载简化版框架之前,我们希望你已经通过上述步骤成功初始化了一个新的 React 应用并能够在 3000 端口访问到欢迎界面。如果已经确认初始化成功,可以通过 该链接 下载压缩包,请在解压后使用压缩包内的 src 文件夹替换你的 React 应用的 src 文件夹,并再次使用 npm start 命令运行 React 应用

如果你现在能够在 3000 端口访问到一个写有 Hello, React! 字样的页面,则已经完成了配置

image-20240122155632922

虚拟 DOM 技术

真实 DOM 和虚拟 DOM 是前端开发中的界面渲染相关的两个概念

真实 DOM 是浏览器创建的文档结构的表示。每个 HTML 元素都是文档对象模型中的一个节点,这些节点以层次结构的形式组织在一起,构成整个页面的结构。操作真实 DOM 可能会引起页面的重绘和重排,因为每次更新都会触发浏览器重新计算布局和绘制页面

虚拟 DOM 是一个在内存中存在的抽象概念,它是对真实 DOM 的一种映射。通过虚拟 DOM,可以在 JavaScript 中描述页面的结构和状态,而不直接操作真实 DOM。优势是在更新页面时,首先操作虚拟 DOM,然后通过比较虚拟 DOM 和上一次渲染时的虚拟 DOM 的差异(称为 diffing),找出需要更新的部分,最后再将这些变化同步到真实 DOM。可以减少直接操作真实 DOM 带来的性能开销,最终只需要对真实 DOM 进行最小限度的修改,从而提高页面渲染的效率

React 是一个使用虚拟 DOM 的典型前端库,这种机制可以使我们更专注于应用的状态和逻辑,而不用过于关心底层的 DOM 操作

React 项目的结构

image-20240122171355674

node_modules

存放项目依赖的第三方库和工具

public

包含不需要 webpack 处理的静态资源,如 HTML 文件、网页图标(favicon)等。其中 index.html 是主 HTML 文件,是 React 项目页面加载的入口文件,后面还会再提到它

src

包含 React 项目的源码,我们主要编写的就是这个文件夹的内容

通常里面还会包含 componentspagesstyles 三个文件夹,分别存放了可复用的 React 组件、页面级的 React 组件、样式文件

App.tsx 是我们自定义的一个组件,通过 export default App; 导出,可以被其他文件引入

接下来重点讲 和 index.tsx

index.tsx

index.tsx 是一个 React 项目中的入口文件,通常用于渲染主应用组件到 HTML 页面:

1
2
3
4
5
6
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(<App />);

解释一下这个文件实现的功能

使用 import 语句导入 React 和 ReactDOM 库,用于创建和渲染 React 组件

使用 import 导入主应用组件 App

使用 ReactDOM.createRoot 方法创建一个 root 对象。这个对象代表了 React 渲染的根节点,对应于页面上的容器元素。再通过 document.getElementById('root') 方法获取到页面的根元素,之后使用 root.render 方法渲染主应用组件 <App /> 到根节点

实例化一个组件也是简单的,类似 HTML 语法,只要像 <App /> 一样用尖括号把组件名包裹起来就可以了

在这个最基本的文件中,<App /> 是一个 React 组件,React 会使用这个组件构建一个虚拟 DOM 树,然后在需要更新时,通过比较新旧虚拟 DOM 树的差异,更新需要更新的部分。但这并不会导致真实 DOM 树的变化,React 会在合适的时机将变化同步到真实 DOM 中,这样可以最小化对真实 DOM 的操作,提高性能。这是 React 中使用虚拟 DOM 的一部分机制

那我们 id 为 root 的根节点在哪里呢?我们打开 public 中的 index.html

image-20240122173348696

诶!根节点就在 index.html。而 React 项目页面加载的入口文件就是 index.html ,这也就解释了为什么页面中可以呈现出我们 returnh1

index.tsx 的作用就是搭建起传统 HTML 和 React TSX 之间的桥梁,让我们可以自由地使用 TSX 语法编写组件,而将这些组件转换到 HTML 的任务则交给了 React 框架

那又一个问题出现了: React 项目的 public 文件夹里的 index.html 并没有 link 到 src 下的 index.tsx ,那 index.tsx 是怎么 get 到 root 这个 id 的呢?

这是因为 React DOM 在运行时会自动查找页面上具有特定 id 的元素,然后将组件渲染到该元素中。具体来说,React DOM 通常会在 index.html 中的 <div id="root"></div> 元素处渲染应用。这个元素是在 index.html 中静态存在的,而不是通过 JavaScript 动态创建的。React 会将应用渲染到这个预定义的根元素上。说白了就是,这套逻辑已经在 React 的原码里写死了

.gitgnore

指定 Git 版本控制系统忽略特定文件或目录的配置文件,很熟悉了,不多介绍

.prettierrc

用于配置 Prettier 工具的文件(这不是 React 项目之一,这只是 Prettier 插件的配置文件,是我自己添加的)

.package-lock.json

npm 在安装包时生成的一个锁定文件,规定了包的安装版本,防止在不同的开发环境上因包的版本不一致出现问题

.package.json

在上一篇《现代前端开发基础》已经讲过

README.md

React 官方写的 README

JSX 和 JS 的区别

JSX 是一种 JavaScript 的语法扩展,也可以说 JSX 是一种方便构建 UI 的语法糖,用于在 JavaScript 代码中描述 UI 结构

JSX 和原生 JavaScript 的最大差别就是引入了标签语法:

1
const p = <p>Hello world!</p>;

这类似于 HTML 标签的语法定义了一个对象,其等价于以下表达:

1
const p = React.createElement("p", undefined, "Hello world!");

既然标签可以是变量,那它就可以被返回:

1
2
3
4
5
6
7
import React from "react";

const App: React.FC = () => {
return <h1>Hello, React!</h1>;
};

export default App;

React.FC 是 React 中的一个泛型类型,用于定义函数组件(Function Components)。在使用 TypeScript 编写 React 组件时,可以使用 React.FC 来明确函数组件的类型
我们注意 return <h1>Hello, React!</h1>; 这行代码,它使得 App 这个函数返回了一个 h1 标签,这种写法我们称为声明式 UI

在 JSX 中,每个 JSX 表达式必须被包裹在一个父元素中。比如这样的代码就会报错:

1
2
3
4
5
6
7
8
9
10
import React from 'react';

const App: React.FC = () => {
return (
<h1>Hello, React!</h1>
<h1>Hello, JSX!</h1>
);
};

export default App;

这是因为 JSX 实际上被转译成 JavaScript 代码,而 JavaScript 中每个表达式只能有一个根节点。

如果真的要返回两个 h1 ,可以考虑将他们放在一个 div 中:

1
2
3
4
5
6
7
8
9
10
11
12
import React from "react";

const App: React.FC = () => {
return (
<div>
<h1>Hello, React!</h1>
<h1>Hello, JSX!</h1>
</div>
);
};

export default App;

如果你不想使用额外的容器元素,你可以考虑使用 React Fragments。React Fragments(片段)是一种特殊的 React 组件,用于在不添加额外 DOM 元素的情况下包裹多个子元素。Fragments 提供了一种解决“JSX 表达式必须具有一个父元素”这一限制的方式。

使用 React Fragments 的语法可以是空标签 <></><React.Fragment></React.Fragment> ,通常使用空标签

1
2
3
4
5
6
7
8
9
10
11
12
import React from "react";

const App: React.FC = () => {
return (
<>
<h1>Hello, React!</h1>
<h1>Hello, JSX!</h1>
</>
);
};

export default App;