YYGod0120
React19-BETACategories: Technology   2024-05-26

React19

4月25日,React官方宣布React19在NPM上推出,可以先行下载使用。 正好此博客也是NextJS搭建的,尝试一下React19带来哪些变化。

准备

官方的建议是先安装稳定版本的React18.3,以在更新到19前发现一些潜藏的问题。 检查无误后,就可以安装React-19(还处于BETA)使用新API。React-v19的升级指南

变化

React在沉寂一长段时间后,听取社区的意见,改掉许多开发痛点以及优化框架。重磅推出了React19

重大的改变有以下几点:

  • React-Compiler:帮助开发人员自动优化页面,减少甚至抛弃useMemo和useCallback。
  • Actions:新的<form>标签以及配套的Hooks表单操作。
  • New Hooks:新的增强操作钩子如use()等等。
  • Document Metadata:现在可以直接在单个组件里编写Meta数据。
  • Web components:React 代码现在将使我们能够合并 Web 组件。

React-Compiler

ReactCompiler可以说是19里最让人激动的东西,它是一个新的编译器,用于帮程序员优化React代码。 比如说先前的useMemo,useCallback等等这一系列的优化钩子,不说使用起来麻烦,使用不当甚至还会造成负优化。于是React推出了Compiler直接自动处理代码,避免了负优化的现象。

简单来说,Compiler做到的事情就是将组件中每个元素,每一个函数都进行缓存,只有当发生变化的时候才会重新缓存,不然就接着使用。 本文主要是说使用而非原理,具体可以查看这篇文章我已彻底拿捏 React Compiler

对于React-Compiler的启用,我们首先要对我们的项目做一个检测。

1npx react-compiler-healthcheck
2

该脚本主要用于检测 1、项目中有多少组件可以成功优化:越多越好 2、是否使用严格模式,使用了优化成功率更高 3、是否使用了与 Compiler 不兼容的三方库

这个框架的检测效果如下:react-server-components对于不同的框架使用Compiler的方法不同,Next启用Compiler需要先下载Next-canary版以及babel-plugin-react-compiler

1npm install next@canary babel-plugin-react-compiler
2

然后在next.config.js:

1// next.config.js
2/** @type {import('next').NextConfig} */
3const nextConfig = {
4  experimental: {
5    reactCompiler: true,
6  },
7};
8
9module.exports = nextConfig;
10

便可以启动compiler对项目进行优化。 成功优化后,可以在React-dev-tool就会看到Memo星星。react-server-components

值得一说的是Compiler还在测试中,存在不少问题,比如与i18n的客户端组件存在一些冲突等,所以有待观望

New Hooks

React19更新许多新的Hooks,包括use(),useOptimistic(),useFormStatus(),绝大多是都是为了Action也就是<form>标签所适配的。

因为没用到表单,所以我只先使用use()

use用于获取资源的值,比如说Promise或者Context。和其他钩子不同,它可以在if语句中使用。

他的具体原理如下(取自官方文档):

当使用 Promise 调用时, use API 会集成 和 Suspense 错误边界。当传递给的 use Promise 处于挂起状态时,组件调用 use 将挂起。如果调用 use 的组件包装在 Suspense 边界中,则将显示回退。 解析 Promise 后,Suspense 回退将替换为使用 use API 返回的数据呈现的组件。如果传递给 use 的 Promise 被拒绝,则将显示最近的错误边界的回退。

基础用法如下:

1const value = use(resource);
2

值得注意的是:

  • use 必须在 Component 或 Hook 中调用 API。
  • 首选在服务器组件中创建 Promise 并将其传递给客户端组件,而不是在客户端组件中创建 Promise。在客户端组件中创建的 Promise 会在每次渲染时重新创建。从服务器组件传递到客户端组件的 promise 在重新渲染时是稳定的。
  • 像useContext一样, use(context)总是在调用它的组件上方寻找最接近的上下文提供程序。它会向上搜索,并且不考虑要从中调用 use(context) 的组件中的上下文提供程序。
  • 将 Promise 从服务器组件传递到客户端组件时,其解析值必须可序列化才能在服务器和客户端之间传递。函数等数据类型不可序列化,并且不能是此类 Promise 的解析值。

在此项目中的使用如下:

1"use client";
2import { Suspense } from "react";
3import { GhostPointer } from "./GhostPointer";
4import { MyTypeWrite } from "./TypeWrite";
5import { DailyWord } from "@/utils/getDailyWord";
6import ErrorBoundary from "./ErrorBoundary";
7
8export function Banner({
9  language,
10  isGetDailyWord,
11  wordsFetch,
12}: {
13  wordsFetch?: Promise<DailyWord>;
14  language: string;
15  isGetDailyWord: boolean;
16}) {
17  return (
18    <ErrorBoundary
19      fallback={
20        <GhostPointer>
21          <span
22            style={{
23              display: "flex",
24              lineHeight: "250px",
25              fontSize: "4rem",
26              justifyContent: "center",
27              color: "white",
28            }}
29          >
30            ⚠️Something went wrong
31          </span>
32        </GhostPointer>
33      }
34    >
35      <Suspense
36        fallback={
37          <GhostPointer>
38            <span
39              style={{
40                display: "flex",
41                lineHeight: "250px",
42                fontSize: "4rem",
43                justifyContent: "center",
44                color: "white",
45              }}
46            >
47              Loading...
48            </span>
49          </GhostPointer>
50        }
51      >
52        <GhostPointer>
53          <MyTypeWrite
54            language={language}
55            wordsFetch={wordsFetch}
56            isGetDailyWord={isGetDailyWord}
57          />
58        </GhostPointer>
59      </Suspense>
60    </ErrorBoundary>
61  );
62}
63

利用ErrorBoundary以及Suspense包裹目标组件,在解析中以及解析失败后有相对应的UI呈现。 再从服务端传入wordsFetch函数再进行use解析。

1//layout
2import { Banner } from "../components/Banner";
3import { getDailyWord } from "@/utils/getDailyWord";
4
5export default async function FrontLayout({
6  children,
7  params: { language },
8}: {
9  children: React.ReactNode;
10  params: { language: string };
11}) {
12  const wordsFetch = getDailyWord();
13  return (
14    <div className="flex flex-col items-center">
15      <div className="w-[100vw]">
16        <Banner
17          language={language}
18          isGetDailyWord={true}
19          wordsFetch={wordsFetch}
20        ></Banner>
21      </div>
22      <section className="w-full">{children}</section>
23    </div>
24  );
25}
26//TypeWrite
27("use client");
28import { usePathname } from "next/navigation";
29import { ReactTyped } from "react-typed";
30import { getDailyWord } from "@/utils/getDailyWord";
31import { Suspense, use, useState } from "react";
32import { DailyWord } from "@/utils/getDailyWord";
33import { splitPathname } from "@/utils/dealPathname";
34import { useTranslation } from "@/app/i18n/client";
35export function MyTypeWrite({
36  language,
37  isGetDailyWord,
38  wordsFetch,
39}: {
40  language: string;
41  isGetDailyWord: boolean;
42  wordsFetch?: Promise<DailyWord>;
43}) {
44  let word;
45  const pathName = usePathname();
46  const title = splitPathname(pathName);
47  const { t } = useTranslation(language, "translations");
48  if (isGetDailyWord && wordsFetch) {
49    const words = use(wordsFetch);
50    word = language === "zh-CN" ? words.note : words.content;
51  }
52  return (
53    <ReactTyped
54      strings={!word ? [t(title)] : [word]}
55      typeSpeed={50}
56      style={{
57        display: "flex",
58        lineHeight: "250px",
59        fontSize: "4rem",
60        justifyContent: "center",
61        color: "white",
62      }}
63    />
64  );
65}
66

最终效果可见博客首页(代码存放于github)

其他

React19的更新远不止于此,目前我只用上这两个方法。

还有关于乐观更新,表单操作等等的钩子尚未使用。

以及令人诟病的Ref转发也得到了优化。

报错提示更人性化等等等等。

在未来会慢慢投入使用,投入生产。

React19官方博客

关于USE

关于Compiler

© 2023 - 2024
githubYYGod0120