再次认识 React

再次认识 React

毕业之后很久没写前端代码了,但最近因为工作需要又开始接触前端项目。例如在 ob-operator 项目中我负责评审两位前端同学过去一年所有的 PR、在用 Cursor 快速搭建软件原型提到的项目中也是用 Cursor 快速实现了一个以 TypeScript + Next.js + Shadcn UI 作为技术栈的 RAG 系统管理后台 Web 应用。

过去自己认为十分枯燥乏味的前端项目开发,在分别许久后似乎又迸发了出新的吸引力。当然如果要回溯让我产生这样变化的原因,我会归因于通过新的路径重新认识 React 吧。这条新路径的载体是 Next.js 这个极度流行的框架。

Next.js

如何改观

在几年前,前端开发框架使用者主要分为 React 和 Vue 两大阵营。恰好过去短暂接手过一个使用 Nuxt.js 的 Vue 项目,当时这个项目给我的感觉是又大又笨,配置复杂,结构僵化。这段经历直接导致了后来我在用 React 时并不会主动接触类似的框架,包括 Next.js 和 Create React App。仅需要 Vite 创建一个 React + TypeScript 的模板就可以开始从头开始添加各种要素:路由、状态管理、样式等,解决方案也都因项目而异。

过去对这种框架并不感兴趣,是因为我认为它们让原本灵活的 React 变得僵化。但是在实际使用 Next.js 后,我发现它的确是一个很好的工具。它提供了很多开箱即用的功能,比如路由、静态导出、服务端渲染等,让我可以更专注于用户交互和业务逻辑的实现,而不用花费太多时间在路由等琐碎的配置上。

世界上本来没有路,走的人多了,也便成了路。Next.js 为 React 应用的开发提供了一条更加清晰的道路,似乎也像容器编排系统一样,用的人多了也就成了事实标准。同时,这种标准化的好处是显而易见的,社区活跃、文档丰富、生态完善,遇到的问题更容易找到解决方案。

当我不排斥使用 Next.js 后,我发现 Vercel 的几个热门项目都挺有意思的,能够极大地提升开发效率和开发体验。DX 这方面 Vercel 做得很好。

服务端组件

Next.js 让全栈应用的开发变得更加流畅简单,特别适合需要 SEO 和性能优化的应用场景。Next.js 在推出 App Router 之后默认的组件是服务端组件(当然可以设置成为客户端组件),这是一种结合了 SSR 和 SSG 优点的新型设计,可以让开发者更加灵活地选择组件的渲染方式,保持良好的开发体验的同时让应用获得更好的性能。

服务端组件是 React 18 引入的一个重要特性。与传统的客户端组件不同,服务端组件在服务器上运行和渲染,这带来了几个重要的优势:

  1. 更好的性能,服务端组件减少了客户端 JavaScript bundle 的大小、避免了不必要的客户端水合(hydration),并且让服务器可以直接访问数据源,减少了客户端请求;
  2. 更简单的数据获取,服务端组件可以直接在组件中进行数据库查询,访问后端服务和 API,并且能够保持敏感信息在服务器端而不会泄露到浏览器中;
  3. 自动代码分割,服务端组件自动进行代码分割,只向客户端发送必要的代码,提高了页面加载速度。

基本的服务端组件示例如下,该组件在服务器上运行,getData 方法可以访问远端服务或者是访问数据库来获取数据并且使用 RSC 响应来渲染组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// app/page.tsx
async function getData() {
const res = await fetch("https://api.example.com/data");
return res.json();
}

export default async function Page() {
const data = await getData();

return (
<main>
<h1>{data.title}</h1>
<p>{data.description}</p>
</main>
);
}

用户在浏览器上访问 /page 页面时,Next.js 会在服务器上运行 Page 组件,并且将渲染结果发送给客户端。这样可以保证页面的首屏渲染速度更快,用户体验更好,也具有很好的 SEO 效果。

编译优化

Next.js 有很多内置的编译优化方案,开发者可以开箱即用地享受这些优化。其中最重要的优化之一是自动代码分割,Next.js 会自动将页面和组件分割成小块,只加载当前页面所需的代码,提高了页面加载速度。

除此之外,Next.js 还提供了一些其他的编译优化功能:

  1. 图片优化: Next.js 内置了图片组件,可以自动进行图片优化,包括调整大小、格式转换等。这个功能特别实用,因为图片往往是影响网页加载速度的重要因素。

  2. 字体优化: 通过 next/font 可以轻松引入并优化字体文件。它会自动内联字体 CSS、消除布局偏移,还能预加载字体文件。

  3. 脚本优化: Next.js 提供了 Script 组件来优化第三方脚本的加载。可以设置加载策略,比如延迟加载或者在空闲时加载,避免阻塞页面渲染。

  4. Tree Shaking: Next.js 会在构建时自动移除未使用的代码,这个功能可以大大减小最终的打包体积。

这些优化虽然看起来很基础,但如果要自己去实现的话其实是比较麻烦的。Next.js 把这些进行了封装,开发者可以省下不少时间专注在业务逻辑上。与此同时,这些优化都是经过实践检验的最佳实践,不用担心踩坑问题。(即使踩坑了也有解决方案,因为社区中的用户足够多)

基于目录结构的路由

Next.js 的路由系统是基于目录结构的,这种设计让路由配置变得更加简单和直观。开发者只需要在 pagesapp 目录下创建文件夹和文件,Next.js 就会自动根据目录结构生成路由。

例如,创建以下文件结构:

1
2
3
4
5
6
pages/
index.tsx
about.tsx
blog/
index.tsx
[id].tsx

这将生成以下路由:

  • / 对应 pages/index.tsx
  • /about 对应 pages/about.tsx
  • /blog 对应 pages/blog/index.tsx
  • /blog/:id 对应 pages/blog/[id].tsx

这种基于文件系统的路由方式不仅简化了路由配置,还使得项目结构更加清晰和直观。

动态路由通过方括号语法实现,例如 [id].tsx 表示一个动态路由参数 id。在组件中可以通过 useRouter 钩子获取路由参数:

1
2
3
4
5
6
7
8
9
10
import { useRouter } from "next/router";

const BlogPost = () => {
const router = useRouter();
const { id } = router.query;

return <div>Post ID: {id}</div>;
};

export default BlogPost;

这种方式使得路由参数的获取和使用变得非常简单。在之前的前端项目里我往往会使用 react-router 这样的库配合 URL 路径来手动管理路由,回过头再看这种基于目录结构的路由方式,确实更加简单和直观。毕竟首要目标是将精力放在业务逻辑上,而不应该过度关注路由配置的“灵活度”。

SWR

SWR 是由 Vercel 团队开发的 React Hooks 库,用于数据获取和请求管理。SWR 的全称是 “stale-while-revalidate”,它的核心思想是先返回缓存(过期的)数据,然后发送请求(重新验证),最后得到最新数据。

SWR 的主要特点包括:

  1. 自动缓存和重新验证:SWR 会自动缓存请求结果,并在需要时重新验证数据。
  2. 实时体验:通过轮询和 WebSocket 支持,SWR 可以实现实时数据更新。
  3. 错误重试:在请求失败时,SWR 会自动进行重试。
  4. 本地数据变更:支持本地数据变更,并自动同步到服务器。
  5. 乐观更新:支持乐观数据更新,提高用户体验。

使用 SWR 的基本示例如下:

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

const fetcher = (url) => fetch(url).then((res) => res.json());

function Profile() {
const { data, error } = useSWR("/api/user", fetcher);

if (error) return <div>Failed to load</div>;
if (!data) return <div>Loading...</div>;

return <div>Hello, {data.name}</div>;
}

在这个示例中,useSWR 钩子用于获取 /api/user 的数据。fetcher 函数定义了如何获取数据。useSWR 返回的数据和错误状态可以用于渲染不同的 UI。

如果希望实现定期更新数据,可以使用 revalidateOnMountrevalidateOnFocus 选项。这样可以在组件挂载和获取焦点时重新验证数据,保持数据的实时性;或者直接使用周期轮询选项 refreshInterval 来指定轮询周期。这些选项在 useSWR Hook 的第三个参数中进行设置。

1
2
3
4
5
6
function Profile() {
const { data, error } = useSWR("/api/user", fetcher, {
refreshInterval: 1000,
});
// ...
}

SWR 官方还贴心的给出了 Understanding SWR 这样一篇文档,向开发者解释它的设计思想和实现原理,感兴趣的朋友可以浏览下这篇图文并茂的文章。

其他前端项目/工具

Shadcn UI

Shadcn UI 是一个基于 Radix UI 和 Tailwind CSS 的组件库,用于构建设计精美且可定制的组件。Shadcn UI 的核心思想是通过 copy-paste 的方式让开发者完全掌控组件的代码。

Shadcn UI 的主要特点包括但不限于下面几点:

  1. 完全透明的组件代码:组件代码可以随意修改,完全透明。
  2. 无额外依赖:不会引入额外的依赖,bundle size 更小。
  3. 简洁优雅的设计:组件设计简洁优雅,符合现代审美。
  4. 现代特性支持:支持深色模式,响应式设计等现代特性。

使用 Shadcn UI 的流程与其他的组件库不太相符,需要先初始化一个项目,然后将组件代码复制到项目中进行修改。这种方式虽然有些不便,但是可以让开发者更好地理解组件的实现细节,从而更好地定制和扩展组件。

首先是初始化 Shadcn UI 的项目,最近接触到一个新的 JS 运行时 bun,同时它也提供了多合一的工具包,在这里我直接使用 bun 来初始化项目。

1
2
# -d 表示使用默认配置
bunx --bun shadcn@latest init -d

然后在项目中安装对应的组件依赖,比如 Button 组件:

1
bunx --bun shadcn@latest add button

这默认会在 components/ui 目录下生成一个 button.tsx 文件,然后可以在项目中使用这个组件:

1
2
3
4
5
import { Button } from "@/components/ui/button";

function App() {
return <Button>Click me</Button>;
}

Shadcn UI 提供了美观且丰富的组件,可以根据需要进行定制和扩展,当然最好到官网查看具体的组件列表和使用方法。之前用过 Ant Design、Element UI 等组件库,但是这些库往往会引入很多额外的依赖,而且组件代码不够透明和灵活、组件样式不够简洁和美观。Shadcn UI 则很好地解决了这些问题。

Lucide Icon

Lucide 是一个开源的图标库,提供了一系列简洁、现代的图标。Lucide 的图标设计简洁明了,适合用于各种 Web 和移动应用的 UI 设计,其中有超过 1500 个图标都是可以免费使用的(ISC 开源协议)。Lucide 的图标提供了 CDN, React, Vue, Svelte 等多种使用方式,几乎可以在任何 Web 项目中使用。

例如下面我就通过 SVG 在我的博客中嵌入了几个 Lucide 的图标:

Playwright

Playwright 是一个用于自动化测试和浏览器操作的 Node.js 库,同时也提供了 Python 等其他语言的 SDK,由 Microsoft 开发并开源。Playwright 支持多种浏览器,包括 Chromium、Firefox 和 WebKit,可以通过一套代码在这些浏览器上进行自动化测试、网页截图、性能分析等操作。

Playwright 的主要特点包括:

  1. 支持多种浏览器:支持 Chromium、Firefox 和 WebKit 等多种浏览器;
  2. 自动化测试:可以进行自动化测试、网页截图、性能分析等操作;
  3. 跨平台:支持 Windows、macOS 和 Linux 等多种操作系统;
  4. 多语言支持:提供了 Node.js、Python、C# 等多种语言的 SDK;
  5. 众多智能优化:支持多种智能优化,比如自动等待、自动重试等。

Playwright 的使用非常简单,例如我们在 Python 中安装 Playwright 的包:

1
2
pip install playwright
playwright install

然后就可以使用 Playwright 进行自动化测试或者是浏览器的自动控制:

1
2
3
4
5
6
7
8
9
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("http://playwright.dev")
print(page.title())
browser.close()

上述代码以同步的方式启动了一个 Chromium 浏览器实例,在新建的页面中访问了 http://playwright.dev 网站,最后打印了页面的标题,并关闭了浏览器。现在有许多 AI 加持的自动化浏览器控制工具都是基于 Playwright 的,比如 browser-use

相比于上一代 Web 自动化控制工具 Selenium,Playwright 提供了更好的性能和更多更现代的功能,比如支持多种浏览器、支持多种语言、支持多种智能优化等,当下的新项目如果需要进行 Web 项目的自动化控制或测试,Playwright 是最好的选择了。

总结

总的来说,重新认识 React 让我对前端开发有了新的热情。Next.js 这个框架确实值得如此热门,它让前端应用或全栈 Web 应用的开发变得更简单、更高效。服务端组件、开箱即用的编译优化、基于目录结构的路由等特性都让人眼前一亮,再加上 Vercel 的 SWR 用作数据管理,整个 Web 应用的开发流程变得顺畅了很多。

另外,重新放开对 React 和 Next.js 及其生态的接触之后,我也发现了一些其他有趣的前端项目和工具,例如 Shadcn UI, Lucide Icon, Playwright 等。在未来类似 Cursor 的开发工具的帮助下,实现前端项目也会变得更加高效和有趣,我们也将有更多的时间和精力去了解更可靠、更有效的前端开发工具和技术,来帮助构建功能更强、易用性更高的新一代 Web 应用。

作者

PowerfooI

发布于

2025-02-15

更新于

2025-02-15

许可协议

评论