前端与后端 / 技术笔记

React 与 Next.js

从组件和状态开始,一步理解 React 如何维护界面,以及 Next.js 如何把它组织成完整 Web 应用。

内容章节
7 节
阅读重点
原理与边界
示例语言
TypeScript

第一次接触 React 时,很容易产生一个问题:

HTML、CSS、JavaScript 已经可以做网页了,为什么还需要 React?

假设现在有一个商品页面:

HTML 示例

markup

<h1>机械键盘</h1><p>¥399</p><button>加入购物车</button>

如果页面一直不变,HTML 已经完全够用。

但用户点击“加入购物车”以后:

文本示意

text

购物车 0购物车 1

点击收藏以后:

文本示意

text

登录以后:

文本示意

text

登录陈酒

页面开始不断随着数据变化。

React 主要解决的,就是:

数据发生变化以后,页面应该如何跟着变化。

可以把 React 理解成一个自动更新的显示屏。

例如库存:

文本示意

text

库存 = 100页面显示:库存 100

卖出一件商品:

文本示意

text

库存 = 99

我们希望页面自动变成:

文本示意

text

库存 99

React 的核心思路可以简单写成:

文本示意

text

UI = f(state)

也就是:

页面是当前状态计算出来的结果。

开发者主要负责描述“现在的数据是什么”和“页面应该长什么样”,React 负责把变化同步到界面。

1. Component:把页面拆成不同区域

假设一个首页包含:

文本示意

text

导航栏个人介绍项目作品技术笔记关于我页脚

如果全部写进一个 page.tsx,几百行以后会越来越难维护。

组件化可以理解成给房子分房间:

文本示意

text

HomePage├── NavBar├── HeroSection├── ProjectsSection├── NotesSection├── AboutSection└── Footer

代码就会变成:

TSX 示例

tsx

export default function HomePage() {    return (        <>            <NavBar />            <HeroSection />            <ProjectsSection />            <NotesSection />            <AboutSection />            <Footer />        </>    );}

HomePage 负责组织页面。

ProjectsSection 负责项目区域。

NavBar 负责导航。

组件化真正解决的是:

职责划分。

组件拆分需要保持合适的粒度。

如果连一个按钮都拆成:

文本示意

text

Button├── ButtonText└── ButtonIcon

过度拆分会增加维护成本。

通常可以问几个问题:

文本示意

text

它有没有独立职责?它会不会重复使用?它有没有自己的状态?它的复杂度是否已经上升?

满足其中几个,再考虑拆成独立组件。

2. Props 和 State

Props 可以理解成:

外面传进来的参数。

比如项目卡片:

TSX 示例

tsx

<ProjectCard    title="陈酒个人技术库"    description="基于 Next.js 构建的个人技术实践网站"/>

titledescription 就是 Props。

它和普通函数参数很像:

TypeScript 示例

typescript

function add(a, b) {    return a + b;}

React 组件可以理解成:

文本示意

text

Props组件UI

State 则是组件自己的“记忆”。

比如图片预览:

文本示意

text

关闭点击图片打开

组件必须记住当前到底是开还是关。

TSX 示例

tsx

const [open, setOpen] = useState(false);

可以理解成 React 帮组件保存了一个小盒子:

文本示意

text

open = false

点击以后:

TSX 示例

tsx

setOpen(true);

React 会重新执行组件,再根据新的状态生成页面。

整个过程就是:

文本示意

text

用户操作State 改变组件重新执行UI 更新

3. React 为什么会重新渲染

“重新渲染”很容易让人误以为整个网页都会重新加载。

实际上更接近:

文本示意

text

旧 UI比较新 UI

例如:

文本示意

text

标题:陈酒个人技术库按钮:0

点击按钮以后:

文本示意

text

标题:陈酒个人技术库按钮:1

React 会发现:

文本示意

text

标题没变按钮数字变了

最终只需要更新真正变化的部分。

所以 React 的工作方式更像:

根据新的状态重新计算页面,再找到需要改变的地方。

4. useEffect:React 和外部世界之间的桥

useEffect 经常被理解成:

页面加载以后执行代码。

这种理解比较容易写乱。

更准确地说:

useEffect 用来让 React 和外部系统同步。

React 自己能管理:

文本示意

text

PropsState组件UI

但浏览器里还有:

文本示意

text

document.titlewindow定时器WebSocket第三方组件

这些属于 React 外部。

例如文章标题变化后,同时修改浏览器标签:

TSX 示例

tsx

useEffect(() => {    document.title = `${title} - 陈酒个人技术库`;}, [title]);

可以理解成:

文本示意

text

React 状态useEffect浏览器

所以如果一个值本来就能直接算出来:

TSX 示例

tsx

const fullName = `${firstName} ${lastName}`;

通常没有必要再用 useEffect 保存一份。

一个实用原则是:

能直接计算,就直接计算;需要和外部系统同步,再考虑 Effect。

5. 为什么还需要 Next.js

React 主要负责:

文本示意

text

组件状态交互界面

但真正做一个网站,还需要解决:

文本示意

text

页面地址是什么?路由怎么组织?页面在哪里渲染?数据从哪里获取?哪些代码运行在服务器?哪些代码运行在浏览器?

可以把 React 类比成汽车发动机。

它解决了核心动力问题。

Next.js 则继续提供:

文本示意

text

路由服务端渲染数据获取服务端逻辑资源优化构建

所以可以简单理解成:

文本示意

text

React负责页面内部怎么工作
Next.js负责整个 Web 应用怎么组织

6. Server Component 和 Client Component

Next.js App Router 中,组件默认可以运行在服务端。

例如技术笔记正文:

文本示意

text

标题正文相关文章

这些内容通常不需要用户交互,可以直接由服务器生成。

可以类比成餐厅后厨:

文本示意

text

读取数据处理内容做好以后端给用户

而类似:

文本示意

text

点击按钮展开菜单图片预览监听页面滚动

必须发生在浏览器中。

这时使用:

TSX 示例

tsx

"use client";

可以把它理解成:

文本示意

text

Server后厨
Client餐桌

服务器负责准备数据和页面。

浏览器负责用户交互。

比如技术笔记页面:

文本示意

text

NotePage              Server├── 标题               Server├── 正文               Server├── 相关文章           Server├── 大纲               Client└── 图片预览           Client

这样职责会比较清楚。

7. React 和 Next.js 最后是怎么配合的

整个过程可以简单理解成:

文本示意

text

用户访问 URLNext.js 找到页面Server Component 获取内容React 生成 UI浏览器显示Client Component 处理交互State 改变React 更新页面

所以学习 React 和 Next.js 时,比记住所有 API 更重要的是慢慢形成这些判断:

文本示意

text

这里需要 State 吗?
这个值能不能直接算?
这里真的需要 useEffect 吗?
这个组件该不该拆?
这个逻辑应该放 Server 还是 Client?
这个 Bug 是代码问题,还是职责已经混乱?

当这些问题逐渐能判断清楚以后,React 和 Next.js 才真正从“会写”进入“理解”。