React 19 总共有多少个 Hooks

23 min read
·

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 的值会延迟一会儿再更新

useDeferredValue demo gif

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

以下是使用的例子:

useId demo img

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 的当前状态。

以下是使用的例子:

useDebugValue demo image

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 配合用来在渲染过程中等待资源读取。

以下是使用的例子:

use demo gif

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,从而提供更快的用户体验。

以下是使用的例子:

useOptimistic demo gif

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} />;
}