React 19 总共有多少个 Hooks
React 19 中可用的 Hooks 完整列表
总共有 19 个分别是:
- 状态管理 Hook
useState
useReducer
- 上下文 Hook
useContext
- 引用 Hook
useRef
useImperativeHandle
- 副作用 Hook
useEffect
useLayoutEffect
useInsertionEffect
- 性能优化 Hook
useMemo
useCallback
- 调度 Hook
useTransition
useDeferredValue
- 其他 Hook
useId
useSyncExternalStore
useDebugValue
- React 19 新增 Hook
use
useActionState
useFormStatus
useOptimistic
useState
useState
允许函数组件拥有和管理自己的状态,使得组件能够响应变化并重新渲染。
以下是使用的例子:
import React, { useState } from 'react';
export default function SimpleCounter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
useReducer
useReducer
用于管理较复杂的状态逻辑。它类似于 useState
,但更适合处理多个相关状态值或当下一个状态依赖于之前的状态时。
以下是使用的例子:
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
useContext
useContext
用于在组件树中共享数据,而不必显式地通过组件树逐层传递 props。它允许组件直接访问在父组件中定义的上下文(Context)。
以下是使用的例子:
import React, { createContext, useContext, useState } from 'react';
const ThemeContext = createContext('light');
function DisplayTheme() {
const theme = useContext(ThemeContext);
return <div>Current theme: {theme}</div>;
}
export default function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<DisplayTheme />
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</ThemeContext.Provider>
);
}
useRef
useRef
用于创建一个可变的引用,其值在组件的整个生命周期内保持不变。它通常用于存储 DOM 元素引用或任何不需要触发重新渲染的可变值。
以下是使用的例子:
import React, { useRef } from 'react';
export default function FocusInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
useImperativeHandle
useImperativeHandle
允许你自定义使用 ref 时暴露给父组件的实例值。这个 Hook 通常与 forwardRef
一起使用,用于向父组件暴露自定义的方法或属性。
这里了看得更清晰,使用 tsx 来编写例子代码
import React, { useRef, useImperativeHandle, forwardRef, useState } from 'react';
type CounterRef = {
increment: () => void;
getCount: () => number;
};
const CustomCounter = forwardRef<CounterRef>((props, ref) => {
const [count, setCount] = useState(0);
// 通过调用这个方法,外部的 ref 持有的是第二个参数返回的对象
useImperativeHandle(ref, () => ({
increment: () => setCount(count + 1),
getCount: () => count
}));
return <div>Count: {count}</div>;
});
export default function App() {
const counterRef = useRef<CounterRef>(null);
const handleClick = () => {
if (counterRef.current) {
counterRef.current.increment();
console.log('Current count:', counterRef.current.getCount());
}
};
return (
<div>
<CustomCounter ref={counterRef} />
<button onClick={handleClick}>Increment and Log Count</button>
</div>
);
}
useEffect
useEffect
用于在函数组件中执行副作用操作。副作用可以是数据获取、订阅或者手动修改 DOM 等。它在组件渲染后运行,可以通过依赖数组控制其执行时机。
以下是使用的例子:
import React, { useState, useEffect } from 'react';
export default function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return <div>Timer: {count} seconds</div>;
}
useLayoutEffect
useLayoutEffect
功能与 useEffect
完全一致,仅在触发时机有所区别,它在所有 DOM 变更之后同步触发,并在浏览器绘制之前完成。由于整个过程是同步的,所以如果 useLayoutEffect
中存在耗时操作则会让页面卡住。在大多数情况下,我们应该优先使用 useEffect
,除非确实需要在绘制之前访问或修改 DOM。
以下是使用的例子:
import React, { useLayoutEffect } from 'react';
export default function LogDOMInfo() {
useLayoutEffect(() => {
const mainElement = document.querySelector('main');
if (mainElement) {
console.log('Main element width:', mainElement.clientWidth);
console.log('Main element height:', mainElement.clientHeight);
}
}, []);
return (
<main>
<h1>Hello, useLayoutEffect!</h1>
<p>Check the console for main element dimensions.</p>
</main>
);
}
useInsertionEffect
useInsertionEffect
是一个专门为 CSS-in-JS 库设计的 React Hook。它在 DOM 变更之后、但在 layout effects 读取新布局之前同步触发。这个 Hook 主要用于在 DOM 变更之前注入样式,以避免布局抖动。
以下是使用的例子:
import React, { useState, useInsertionEffect } from 'react';
// 模拟一个简单的 CSS-in-JS
const injectStyle = (css: string) => {
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
return style;
};
export default function DynamicStyle() {
const [color, setColor] = useState('red');
useInsertionEffect(() => {
const style = injectStyle(`
.dynamic-text {
color: ${color};
font-size: 24px;
font-weight: bold;
}
`);
return () => {
document.head.removeChild(style);
};
}, [color]);
return (
<div>
<p className="dynamic-text">This text has dynamic styling</p>
<button onClick={() => setColor(color === 'red' ? 'blue' : 'red')}>
Toggle Color
</button>
</div>
);
}
useMemo
useMemo
用于缓存计算结果。它接受一个创建函数和一个依赖项数组,只有当依赖项发生变化时,才会重新计算结果。这对于优化需要大量计算的值很有用。
以下是使用的例子:
import React, { useState, useMemo } from 'react';
function expensiveCalculation(num: number): number {
console.log('Calculating...');
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
}
export default function Calculator() {
const [num, setNum] = useState(1);
// expensiveResult 这个结果只会在 num 变化的时候重新计算,如果 num 不变,那么就不会重新计算
const expensiveResult = useMemo(() => expensiveCalculation(num), [num]);
return (
<div>
<h2>Expensive Calculation Result: {expensiveResult}</h2>
<button onClick={() => setNum(num + 1)}>Change Calculation Input</button>
</div>
);
}
useCallback
useCallback
用于缓存函数定义。它接受一个回调函数和一个依赖项数组,只有当依赖项发生变化时,才会返回一个新的函数。这对于优化将函数作为 props 传递给子组件的场景很有用。
以下是使用的例子:
下面的例子在控制台能看到 Button "Button 2" rendered
但看不到 Button "Button 1" rendered
,这就是因为 useCallback 的依赖数组不变,所以他返回的函数也不会变,而不是每次都重新创建
import React, { useState, useCallback, memo } from 'react';
const ExpensiveButton = memo(
({ onClick, label }: { onClick: () => void; label: string }) => {
console.log(`Button "${label}" rendered`);
return <button onClick={onClick}>{label}</button>;
},
);
export default function CallbackExample() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const incrementCount1 = useCallback(() => {
setCount1((prevCount) => prevCount + 1);
}, []);
const incrementCount2 = () => {
setCount2((prevCount) => prevCount + 1);
};
console.log('Parent component rendered');
return (
<div>
<h2>With useCallback: {count1}</h2>
<ExpensiveButton onClick={incrementCount1} label="Button 1" />
<h2>Without useCallback: {count2}</h2>
<ExpensiveButton onClick={incrementCount2} label="Button 2" />
<p>Check the console to see which components are re-rendering.</p>
</div>
);
}
useTransition
useTransition
提供了一个标志位用来表明加载状态,开发者可以用这个标志位改善用户体验,特别是在处理可能导致界面卡顿的较慢更新时会很有用。
以下是使用的例子:
import React, { useState, useTransition } from 'react';
function SlowList({ text }: { text: string }) {
const items = [];
for (let i = 0; i < 500; i++) {
items.push(<li key={i}>{text}</li>);
}
return <ul>{items}</ul>;
}
export default function TransitionExample() {
const [text, setText] = useState('');
const [deferredText, setDeferredText] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setText(e.target.value);
startTransition(() => {
setDeferredText(e.target.value);
});
}
return (
<div>
<input value={text} onChange={handleChange} />
<p>Typed text: {text}</p>
{isPending ? (
<p>Updating list...</p>
) : (
<SlowList text={deferredText} />
)}
</div>
);
}
useDeferredValue
useDeferredValue
用于延迟更新某个值,它接受一个值并返回该值的新副本,该副本将延迟更新。这对于推迟渲染开销大的部分很有用。
下面的例子中,如果快速输入内容或者删除内容,deferredText 的值会延迟一会儿再更新
import React, { useState, useDeferredValue, memo } from 'react';
const SlowList = memo(function SlowList({ text }: { text: string }) {
console.log('Rendering SlowList');
const items = [];
for (let i = 0; i < 10000; i++) {
items.push(<li key={i}>{text}</li>);
}
return <ul>{items}</ul>;
});
export default function DeferredValueExample() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setText(e.target.value);
}
return (
<div>
<input
value={text}
onChange={handleChange}
placeholder="Type here..."
style={{ fontSize: '18px', padding: '5px', width: '200px' }}
/>
<p>Immediate text: {text}</p>
<p style={{ color: text === deferredText ? 'black' : 'red' }}>
Deferred text: {deferredText}
</p>
<SlowList text={text} />
</div>
);
}
useId
useId
用于生成唯一的 ID
以下是使用的例子:
import React, { useId } from 'react';
function LabeledInput({ label }: { label: string }) {
const id = useId();
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} type="text" />
</div>
);
}
export default function UniqueIdExample() {
return (
<div>
<h2>Form with Unique IDs</h2>
<LabeledInput label="First Name" />
<LabeledInput label="Last Name" />
<LabeledInput label="Email" />
</div>
);
}
useSyncExternalStore
useSyncExternalStore
用于订阅外部数据源,用于处理 React 和外部状态库之间进行集成。
以下是使用的例子:
import React, { useSyncExternalStore } from 'react';
// 模拟一个外部存储
const createStore = (initialState: number) => {
let state = initialState;
const listeners = new Set<() => void>();
return {
subscribe: (listener: () => void) => {
listeners.add(listener);
return () => listeners.delete(listener);
},
getSnapshot: () => state,
increment: () => {
state++;
listeners.forEach(listener => listener());
}
};
};
const store = createStore(0);
function Counter() {
const count = useSyncExternalStore(
store.subscribe,
store.getSnapshot
);
return (
<div>
<p>Count: {count}</p>
<button onClick={store.increment}>Increment</button>
</div>
);
}
export default function ExternalStoreExample() {
return (
<div>
<h2>External Store Example</h2>
<Counter />
<Counter />
</div>
);
}
useDebugValue
useDebugValue
用于在 React DevTools 中为自定义 Hooks 添加标签。它允许开发者为自定义 Hook 提供更有意义的调试信息,使得在使用 React DevTools 检查组件时更容易理解 Hook 的当前状态。
以下是使用的例子:
import React, { useState, useEffect, useDebugValue } from 'react';
// 自定义 Hook: useOnlineStatus
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
// 使用 useDebugValue 添加调试信息
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
function StatusIndicator() {
const isOnline = useOnlineStatus();
return (
<div>
<h2>Network Status</h2>
<p>You are {isOnline ? 'online' : 'offline'}</p>
</div>
);
}
export default function DebugValueExample() {
return (
<div>
<h1>useDebugValue Example</h1>
<StatusIndicator />
</div>
);
}
use
use
是 React 的一个新 Hook,和 Suspense
配合用来在渲染过程中等待资源读取。
以下是使用的例子:
import React, { use, Suspense } from 'react';
// 模拟异步数据加载
const fetchComments = () =>
new Promise((resolve) =>
setTimeout(() => resolve(['Comment 1', 'Comment 2', 'Comment 3']), 1000),
);
function Comments() {
const comments = use(fetchComments());
return (
<ul>
{comments.map((comment, index) => (
<li key={index}>{comment}</li>
))}
</ul>
);
}
export default function UseExample() {
return (
<div>
<h1>Comments</h1>
<Suspense fallback={<div>Loading comments...</div>}>
<Comments />
</Suspense>
</div>
);
}
useActionState
useActionState
用于管理表单提交的状态,传统的方式需要手动创建多个状态来保存表单数据,使用 useActionState
则无需手动管理。
以下是使用的例子:
'use client';
import React, { useActionState } from 'react';
// 模拟一个服务器动作
async function submitForm(prevState, formData) {
// 模拟网络请求
await new Promise((resolve) => setTimeout(resolve, 1000));
const email = formData.get('email');
if (!email.includes('@')) {
return { error: 'Invalid email address' };
}
return { success: true, message: 'Form submitted successfully' };
}
export default function ActionStateExample() {
const [state, formAction, pending] = useActionState(submitForm, {
error: null,
success: false,
message: '',
});
return (
<div>
<h1>useActionState Example</h1>
<form action={formAction}>
<div>
<label htmlFor="email">Email:</label>
<input type="email" id="email" name="email" required />
</div>
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
</form>
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
{state.success && <p style={{ color: 'green' }}>{state.message}</p>}
</div>
);
}
useFormStatus
useFormStatus
用于获取最近的 <form>
祖先元素的状态信息。例如在构建 SubmitButton
的过程中可以用来改变 SubmitButton
的样式。
以下是使用的例子:
import React from 'react';
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button
type="submit"
disabled={pending}
className={`px-4 py-2 rounded ${pending ? 'bg-gray-400' : 'bg-blue-500 hover:bg-blue-600'} text-white`}
>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
function handleSubmit(formData) {
// 模拟异步操作
return new Promise((resolve) => {
setTimeout(() => {
console.log('Form submitted with:', Object.fromEntries(formData));
resolve(null);
}, 2000);
});
}
export default function FormStatusExample() {
return (
<form action={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-700"
>
Name:
</label>
<input
type="text"
id="name"
name="name"
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
/>
</div>
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700"
>
Email:
</label>
<input
type="email"
id="email"
name="email"
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
/>
</div>
<SubmitButton />
</form>
);
}
useOptimistic
useOptimistic
用于实现乐观更新,它允许你在等待异步操作完成时立即更新 UI,从而提供更快的用户体验。
以下是使用的例子:
import { useOptimistic, useState, useRef } from 'react';
async function deliverMessage(message) {
await new Promise((resolve) => setTimeout(resolve, 2000));
return message;
}
function Thread({ messages, sendMessage }) {
const formRef = useRef();
async function formAction(formData) {
addOptimisticMessage(formData.get('message'));
formRef.current.reset();
await sendMessage(formData);
}
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [
...state,
{
text: newMessage,
sending: true,
},
],
);
return (
<>
{optimisticMessages.map((message, index) => (
<div key={index}>
{message.text}
{!!message.sending && <small> (Sending...)</small>}
</div>
))}
<form action={formAction} ref={formRef}>
<input type="text" name="message" placeholder="Hello!" />
<button type="submit">Send</button>
</form>
</>
);
}
export default function App() {
const [messages, setMessages] = useState([
{ text: 'Hello there!', sending: false, key: 1 },
]);
async function sendMessage(formData) {
const sentMessage = await deliverMessage(formData.get('message'));
setMessages((messages) => [...messages, { text: sentMessage }]);
}
return <Thread messages={messages} sendMessage={sendMessage} />;
}