就在前三天,本大二的苦逼学生的团队在给老外做项目的时候,因为他要求使用shadcn,作为团队唯一一个前端切图仔,只能去熬夜修炼。 但是在我安装的时候,让我惊讶的并非shadcn的特性,而是
哦里shit,这是什么?React19!😮🚀
所以我直接放弃学习shadcn(其实大一的时候就用过了),转头就去了解React19新特性。
useEffect 是一个功能强大的 hook,但他又是最难驾驭的一个 hook,理解不够的开发者可能会由于滥用它而导致项目失控。
包括被讨论最多的闭包问题,也往往跟它有关。
其中最考验开发者水平的,是对于 useEffect 依赖项的正确处理。
React19 的 大部分更新,几乎都是围绕如何在开发中尽量不用或者少用 useEffect 来展开。
作为使用过React的开发者来说,useEffect 是我们处理异步问题必须使用的重要 hook 之一,他几乎存在于每一个页面组件之中。
React 19 则引入了新的 hook,让我们处理异步开发时,不再需要 useEffect!!!🤩,从而极大的改变我们的开发方式。
React19将给我们的UI交互方式带来极大的改变
use
use() 是 React19 提升异步开发体验最重要的 hook。也是让 useEffect 重要性大幅度削弱的主要原因。
我们可以利用 use 读取 Promise 中的值。也可以使用 use 读取 context 中的资源
正确理解 promise
const value = use(promise);
这里我们需要特别注意的是,Promise 是指的一个已经创建好的 Promise 对象,并且,在该 promise 对象中已经有了确定的 resolve 的结果,use 读取的是 resolve 的值。
接下来看下这两种写法
已经有了状态的Promise
import React from 'react'
import { use } from 'react'
import { resolve } from 'styled-jsx/css'
const page = () => {
const api2 = new Promise((resolve)=>{
resolve({value:2})
})
const result = use(api2)
return (
<div>
<h1>{result.value}</h1>
</div>
)
}
export default page
函数运行时候创建的Promise
function _api3() {
return new Promise((resolve) => {
resolve('api3')
});
}
const result3 = use(_api3());
console.log(result3);
注意:这种方法执行之后会立即返回一个带有 resolve 结果状态的 Promise,但是 use 并不能第一时间读取到其值,运行后会报错
async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding
'use client' to a module that was originally written for the server.
注意:如果你使用nextjs的话,是可以正常运行的。这里我是用的是vite
在条件判断中使用
和其他 hook 一样,use() 必须在函数组件中使用。
但是很不一样的是,use 可以在循环和条件判断语句中使用。我们这里如下案例来演示这个结论。
在这个例子中,use 被使用在 if(!loading) 条件判断中来获取 result 的值。
import { use } from 'react';
import { useState } from 'react';
import Message from './Message';
import Button from './Button';
import Skeleton from './Skeleton';
const _api2 = new Promise((resolve) => {
resolve({ value: 'Unlike React Hooks, use can be called within loops and conditional statements like if. Like React Hooks, the function that calls use must be a Component or Hook.' });
});
const page = () => {
const [loading, setLoading] = useState(false);
let result = { value: '' };
if (!loading) {
result = use(_api2);
}
return (
<>
{loading ? <Skeleton /> : <Message message={result.value} />}
<div>
<Button signal onClick={() => setLoading(!loading)}>切换</Button>
</div>
</>
);
}
export default page
Suspense
如果直接使用 use 获取未直接创建的 Promise 中的值,会抛出一个异常
function _api3() {
return new Promise((resolve) => {
resolve({ value: '_api3' });
});
}
// bad: get an error
const result = use(_api3());
但是实际上在开发过程中,大多数情况都是这种并没有直接得到 Promise resolve 的结果状态,那我们应该怎么办呢?
这个时候我们可以利用 Suspense 来解决这个问题。
Suspense 可以捕获这种异常。我们来看一下这段代码
import { Suspense, use } from 'react';
import Message from './Message';
function _api3() {
return new Promise((resolve) => {
resolve({ value: 'React does not preserve any state for renders that got suspended before they were able to mount for the first time. When the component has loaded, React will retry rendering the suspended tree from scratch.' });
});
}
export default function Demo01() {
const promise = _api3();
return (
<Suspense fallback="">
<Content promise={promise} />
</Suspense>
);
}
function Content(props) {
const { value } = use(props.promise);
return (
<Message message={value} />
);
}
在这段代码中,为了让 Suspense 捕获更小范围的组件,我们单独定义了一个子组件 Content 来使用 use 获取 promise 中的数据。
这也是未来使用的比较常规的思路和手段。
当然,在开发中更常见的效果是使用 use 读取异步 promise,主要的场景就是接口请求。
Suspense 能够捕获到子组件首次渲染的异常。
因此我们常常将 Suspense 当成一种组件错误边界来处理。
但是需要注意的是,传递给 Suspense 的异步组件必须在报错时返回一个 Promise 对象,它才能正常工作。
在 React 19 中,use(promise)
被设计成完全符合 Suspense 规范的 hook,因此我们可以轻松的结合他们两者来完成页面开发。
我们定义一个子组件,该子组件接受一个 promise 作为参数。然后在子组件内部,我们使用 use 读取该 promise 中的值。
有了这个子组件之后,我们使用 Suspense 包裹捕获该组件的错误,防止错误溢出到更高层级的组件。
当 Message 组件首次渲染时,由于直接读取 promise 导致报错,Suspense 捕获到该异常后,会渲染 fallback 中设置的组件。
假如此时我们设置了一个骨架屏 Skeleton 组件
因此,这个案例的视觉表现应该为:1. 首先渲染 Skeleton 组件。然后请求成功之后,use 渲染 Message 组件。
原文:https://juejin.cn/post/7449549889166147596