use
- 作用: 帮你获取
Context
或者Promise
的值
1、获取 Context
替代 useContext
ts
import { createContext, use } from 'react';
const ThemeContext = createContext('light');
// 省去 Provider 的逻辑
function Button() {
// 不需要使用 useContext,直接用 use
const theme = use(ThemeContext);
// 原先是
const theme = useContext(ThemeContext)
return <button className={theme}>按钮</button>;
}
2、获取 Promise
本质是替代 useEffect 和 useStata,但是也有限制,比如直接获取使用 use
获取 Promise 会报错, 比如
ts
// api.ts
export function fetchData(shouldFail = false) {
return new Promise((resolve, reject) => {
if (shouldFail) {
reject(mockData.error);
} else {
resolve(mockData.success);
}
});
}
ts
import { Suspense,use } from 'react';
import { fetchData } from './api'
export function App(){
const dataPromise = use(fetchData())
}
我原本以为也是这么用得,发现包这个错误
这是因为客户端组件不支持直接使用 async/await 或 Promise,React 19 区分了服务端组件和客户端组件的能力。use 只能获取已经 resolve 的值,对于是 Pending 的状态会报错,所以我们要结合 Suspense 来使用。
在不用 Suspense 前,我们可以等 Promise 请求结束后使用 use
ts
let [loading, setLoading] = useState(true);
const promise = useRef(
fetchData().then((res) => {
setLoading(false);
return res;
})
);
if (!loading) {
const data = use(promise.current);
// 正常能够获取到值
console.log('===data===', data);
}
使用 Suspense 和 use
ts
// 服务器端组件
import { Suspense, use } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import './styles.css';
import { fetchData } from './api';
import Data from './Data';
// 处理加载状态
export default function App() {
const promise = fetchData();
return (
<div className="server-page">
<h1>服务端组件示例</h1>
<Suspense fallback={<div>加载中...</div>}>
<Data promise={promise} />
</Suspense>
</div>
);
}
ts
// 客户端组件
import { use } from 'react';
export default function App({ promise }) {
const data = use(promise);
return (
<div className="data-content">
<h3>{data.title}</h3>
<p>{data.content}</p>
</div>
);
}
服务器组件将 Promise 通过 prop 传给客户端组件,客户端组件通过 use
API 来接收 Promise 已经 resolve 的结果。Promise resolve 之前的状态呢,在 Suspense 里面的 fallback 处理好了
使用 async await
以上的写法属于在客户端使用 use
解析 Promise,也可以在服务器端使用 async await解析 Promise
ts
// 服务器端组件
export default async function App() {
const data = await fetchData();
return <Data initData={data}/>
}
但是在服务器端使用 async await 会在 await 之前阻塞渲染,如果将 Promise 传给客户端则不会
之前的写法
ts
export default function App() {
const promise = fetchData();
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
promise.then((res) => {
setData(res);
setLoading(false);
});
}, []);
}
之前的这种写法,通常是封装一个 hooks
ts
// useFetch
function useFetch() {
const [content, update] = useState({value: ''})
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchData().then(res => {
setLoading(false)
update(res)
})
}, [])
return {content, loading}
}
实际场景1:点击更新数据
ts
export default function App() {
// 这个地方要赋 Promise 初值,否则会报错
const [promise, update] = useState(Promise.resolve([]));
const [isLoading, setIsLoading] = useState(false);
function updateData() {
const newPromise = fetchData3();
update(newPromise);
}
return (
<div>
<div className="text-right mb-4">
<button className="button" onClick={updateData} >
<div>更新数据</div>
</button>
</div>
<ErrorBoundary fallback={<div>加载失败</div>}>
<Suspense fallback={<div>加载中...</div>}>
<Data promise={promise} />
</Suspense>
</ErrorBoundary>
</div>
);
}
实际场景 2:初始化数据,然后点击更新
ts
export default function App() {
// 在这里改变
const [promise, update] = useState(fetchData());
const [isLoading, setIsLoading] = useState(false);
function updateData() {
const newPromise = fetchData();
update(newPromise);
}
return (
<div>
<div className="text-right mb-4">
<button className="button" onClick={updateData} >
<div>更新数据</div>
</button>
</div>
<ErrorBoundary fallback={<div>加载失败</div>}>
<Suspense fallback={<div>加载中...</div>}>
<Data promise={promise} />
</Suspense>
</ErrorBoundary>
</div>
);
}
原先是
ts
useEffect(() => {
updateData();
}, []);
const updateData = async () => {
const data = await fetchData2();
update(data);
};
实际场景 3: 把 loading 状态放在 button 上面
也需要把 loading 状态单独存起来
ts
export default function ServerComponent() {
const [loading, setLoading] = useState(false);
const [promise, setPromise] = useState(initialPromise);
const updateData = () => {
setLoading(true);
// 创建新的 Promise
const newPromise = fetchData2();
setPromise(newPromise);
// 当 Promise 完成时更新 loading 状态
newPromise.then(() => {
setLoading(false);
}).catch(() => {
setLoading(false);
});
};
return (
<div className="server-page">
<h1>服务端组件示例</h1>
<div className="button-container">
<button
className={`update-button ${loading ? 'loading' : ''}`}
onClick={updateData}
disabled={loading}
>
{loading ? (
<>
<span className="spinner-small"></span>
<span>加载中...</span>
</>
) : (
<span>更新数据</span>
)}
</button>
</div>
<ErrorBoundary fallback={<div className="error-message">加载失败</div>}>
<Suspense>
<Data promise={promise} />
</Suspense>
</ErrorBoundary>
</div>
);
}
实际场景 4:新增数据
ts
export default function App() {
const [loading, setLoading] = useState(false);
const [promise, setPromise] = useState(fetchData4());
// 修复按钮点击处理函数
const updateData = () => {
setLoading(true);
// 创建新数据
const newData = {
id: Date.now(),
title: `新数据 ${Date.now()}`,
content: "这是新添加的数据"
};
// 创建新的 Promise
const newPromise = fetchData4(newData);
// 更新 Promise 状态
setPromise(newPromise);
// 完成后更新加载状态
newPromise.then(() => {
setLoading(false);
}).catch(() => {
setLoading(false);
});
};
return (
<div className="server-page">
<h1>服务端组件示例</h1>
<div className="button-container">
<button
className={`update-button ${loading ? 'loading' : ''}`}
onClick={updateData}
disabled={loading}
>
<div>{loading ? '加载中...' : '添加新数据'}</div>
</button>
</div>
<ErrorBoundary fallback={<div className="error-message">加载失败</div>}>
<Suspense fallback={
<div className="loading">
<div className="loading-spinner"></div>
<p>加载中...</p>
</div>
}>
<Data promise={promise} />
</Suspense>
</ErrorBoundary>
</div>
);
}
并发 API
useDeferredValue
useDeferredValue 在 React 19 中增加一个 initialValue?
的属性
看一个使用 useDeferredValue 处理搜索的案例
ts
// index.tsx
import { Suspense, useDeferredValue, useState } from "react";
import { Input } from "~/components/ui/input";
import { SearchResults } from "./SearchResults";
import { getUsersInfo } from "~/api";
export default function SearchComponent() {
const [promise, setPromise] = useState(getUsersInfo());
const deferred = useDeferredValue(promise);
const isStale = promise !== deferred;
const handleSearch = (value: string) => {
setPromise(getUsersInfo(value));
};
return (
<div className="search-container p-4 max-w-3xl mx-auto">
<h1 className="text-2xl font-bold mb-4">文章搜索</h1>
<div className="mb-4 relative">
<Input
className="w-full p-2 border border-gray-300 rounded"
onChange={(e) => handleSearch(e.target.value)}
placeholder="搜索文章标题或内容..."
/>
</div>
<Suspense fallback={<div className="text-center p-4">加载中...</div>}>
<div
style={{
opacity: isStale ? 0.5 : 1,
transition: isStale
? "opacity 0.2s 0.2s linear"
: "opacity 0s 0s linear",
}}
>
<SearchResults promise={deferred} />
</div>
</Suspense>
</div>
);
}
typescript
// searchResult.tsx
import { use } from "react";
// 定义帖子类型
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
export function SearchResults({ promise }: { promise: Promise<any> }) {
const posts = use(promise) as Post[];
if (!posts || posts.length === 0) {
return <div className="text-center p-4">没有找到匹配的文章</div>;
}
return (
<div className="user-results">
<h2 className="text-xl font-semibold mb-2">搜索结果 ({posts.length})</h2>
<ul className="divide-y divide-gray-200">
{posts.map((post) => (
<li key={post.id} className="py-3">
<div className="flex flex-col">
<div className="font-medium">{post.title}</div>
<div className="text-sm text-gray-500">{post.body}</div>
</div>
</li>
))}
</ul>
</div>
);
}
typescript
// api.ts
export function getUsersInfo(query: string = "") {
return new Promise(async (resolve, reject) => {
try {
// 添加一个假延迟来让等待更加明显
await new Promise((r) => {
setTimeout(r, 500);
});
console.log("搜索关键词:", query);
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts"
);
const posts = await response.json();
if (query && query.trim() !== "") {
const lowercaseQuery = query.toLowerCase();
const filteredPosts = posts.filter(
(post: any) =>
post.title.toLowerCase().includes(lowercaseQuery) ||
post.body.toLowerCase().includes(lowercaseQuery)
);
resolve(filteredPosts);
} else {
resolve(posts);
}
} catch (error) {
console.error("获取数据失败:", error);
reject(error);
}
});
}
这个 hook 在场景搜索输入的时候很有用诶,一般来说我们实时输入检索,会通过实时防抖节流频繁进行网络请求,如果是减少网络请求次数,这些也是有用的。但是搜索是一边输入一边渲染结果,由于防抖节流并能不能中断渲染,所以它的渲染进程是阻塞的
- 防抖 是指在用户停止输入一段时间(例如一秒钟)之后再更新列表。
- 节流 是指每隔一段时间(例如最多每秒一次)更新列表
实现原理: 使用 useDeferredValue(value),首先会为该值创建一个延迟副本,视为优先级低的渲染任务,当用户再次实时输入的时候,这个时候的渲染任务是优先级别高的,useDeferredValue 会中断优先级别比较低的任务,去渲染优先级别高的任务。
优势:
- 处理不必要的渲染,通过延迟更新低优先,可以直接跳过中间态,直接渲染终态
- 减少闪烁:通过平滑的过渡,比如在页面中的半透明变化
useTransition
useDeferredValue 在内部实际上使用了类似 useTransition 的机制,但有一些关键区别:
- useTransition 用于标记整个状态更新为低优先级。
- useDeferredValue 专门用于创建某个值的低优先级副本
废弃 forwardRef
现在
ts
// MyInput
import { forwardRef } from "react";
const MyInput = forwardRef<HTMLInputElement, { placeholder?: string }>(function MyInput(props, ref) {
const { placeholder } = props;
return <input placeholder={placeholder} ref={ref} />;
});
export default MyInput;
// 父组件
<div>
<MyInput ref={inputRef} placeholder="输入内容" />
<button onClick={() => inputRef.current?.focus()}>
聚焦输入框
</button>
之后
ts
// 父组件
<div>
<MyInput ref={inputRef} placeholder="输入内容" />
<button onClick={() => inputRef.current?.focus()}>
聚焦输入框
</button>
</div>
// MyInput
import type { RefObject } from "react";
const MyInput = (props:{placeholder:string,ref:RefObject<HTMLInputElement | null>}) => {
return <input placeholder={props.placeholder} ref={props.ref} />;
};
export default MyInput;
Context 直接作为 Provider, 而不是之前的 ThemeContext.Provider, 读取的时候直接用 use(ThemeContext), 而且直接可以在条件语句显示
ts
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
React Compiler
React Compiler(以前叫 React Forget),减轻开发者手动优化组件的负担,比如 useCallback、 useMemo、React.memo。 React 通过静态分析,自动在编译的时候优化这些代码,让开发者专注业务逻辑,而不是性能优化
核心概念
- 自动优化:就是针对于以前要进行 useCallback、 useMemo、 React.memo 手动优化的地方变成自动
- 细粒度的跟踪:能够精确地识别组件中哪些依赖需要更新
- 编译时优化:在构建过程中进行优化,不会增加运行时的负担,生成的代码报了所有的优化
这个网站可以看到 React playground 的代码,
关键词: Symbol.for
form actions
支持将函数作为 form 的 action 属性
ts
<form action={actionFunction}>
<div class="flex items-center">
<label className="w-[100px]">邮箱: </label>
<input type="text" name="email"/>
</div>
<div class="flex items-center">
<label className="w-[100px]">名字: </label>
<input type="text" name="name"/>
</div>
</form>
function actionFunction(formData:FormData){
const email = formData.get("email");
const name = formData.get("name");
}
useFormStaus
kotlin
const { pending, data, method, action } = useFormStatus();
注意:
- useFormStatus只能在 form 的子组件中使用
- useFormStatus 不接受任何参数
useActionState
- 简化数据操作状态管理
useActionState
是一个可以根据某个表单动作的结果更新 state 的 Hook。 React.useActionState
在 Canary 版本中曾被称为 ReactDOM.useFormState
使用方法
ts
useActionState(action, initialState, permalink?)
javascript
const [formState, formAction] = useActionState(submit, {});
return (
<Form action={formAction} />
)
// action.ts
const submit = (state,formData)=>{
}
- 使用 useActionState 包括 action 的时候, action它的第一个参数是 state, useFormStatus 的 action 第一个参数是 formdata
useOptimistic
- 用于乐观更新 UI
什么是乐观更新
- 传统方式
rust
用户操作 -> 发送请求 -> 等待响应 -> 更新 UI
- 乐观更新
rust
用户操作 -> 更新 UI -> 发送请求 -> 等待响应,失败回滚,成功更新
优势
- 及时反馈
- 更好的用户体验
使用场景
- 社交媒体的点赞关注
- 待办事项的添加/删除
- 评论的发布
- 购物车操作
- 。。。
注意事项
- 错误处理:需要妥善处理错误情况下的回滚机制,最好是服务器成功的概率很大
- 状态管理:需要良好的状态管理处理临时状态,乐观更新的数据不是传给后端的,需要临时存数据的地方
- 数据一致性:和服务器数据一致
- 能提升用户体验,但是需要权衡使用场景和复杂度
API
javascript
export function useOptimistic<State, Action>(
passthrough: State,
reducer: (state: State, action: Action) => State,
): [State, (action: Action) => void];