17 个驱动现代组件 90% 的 React Hooks
17 个驱动现代组件 90% 的 React Hooks

1. useState

这个钩子是不可避免的。

当你需要跨重新渲染持久化的动态状态时,这就是你要找的。

const [count, setCount] = useState(0);

2. useReducer

当你的状态变得复杂(例如多个相关字段)时,useReducer 就成了你的好帮手。

它也非常适合与 useContext 配合使用,让你可以通过简单的 dispatch 调用更新状态。

const reducer = (state, action) => {
  switch (action.type) {
    case "addTodo":
      return { todos: [...state.todos, action.todo] };
    case "removeTodo":
      // TODO: Implement logic
    case "toggleTodo":
      // TODO: Implement logic
    default:
      return state;
  }
};

const [state, dispatch] = useReducer(reducer, { todos: [] });

3. useContext

当属性穿透失控时,这个钩子拯救了局面。

Prop drilling = 将 props 通过多层传递,只是为了到达一个深层嵌套的组件。

使用 useContext 来:

  • 在小型应用中管理全局状态
  • 共享数据,如认证用户
  • 避免手动到处传递 props
const UserContext = createContext();

function App() {
  const user = useUser();
  return (
    <UserContext.Provider value={user}>
      {/* ... */}
    </UserContext.Provider>
  );
}

function Profile() {
  const user = useContext(UserContext);
  return <div>Hello {user.name}</div>;
}

4. useEffect

99%的情况下你应该避免这样做。 (参见: 你可能不需要一个 Effect)

但在现实生活中,你仍然需要它——尤其是用于与外部系统同步等事情。

function useSetDocumentTitle(title: string) {
  useEffect(() => {
    document.title = title;
  }, [title]);
}

5. useRef

你永远不应该在 React 中直接操作 DOM。

但当你必须使用时(例如 canvas、图表库等),useRef 是安全的方式。

function ExampleChart({ data }) {
  const canvasRef = useRef();

  useEffect(() => {
    const ctx = canvasRef.current;
    if (!ctx) return;

    const chart = new Chart(ctx, {
      type: "bar",
      data: {
        labels: data.map((row) => row.year),
        datasets: [{ label: "Likes", data: data.map((r) => r.count) }],
      },
    });

    return () => chart.destroy();
  }, []);

  return <canvas ref={canvasRef} />;
}

6. useMemo / useCallback

这些可能会因为新的 React 编译器而变为可选的——但目前它们仍然很有用。

当 useMemo 时:

  • 昂贵的计算不应该在每次渲染时重新运行
  • 你正在记忆一个非原始依赖项
  • 你正在向 memo 包装的组件传递值

使用 useCallback 来缓存函数

const total = useMemo(() => computeTotal(items), [items]);

const handleClick = useCallback(() => {
  console.log("clicked");
}, []);

7. useLayoutEffect

这一种比较少见但很有用。

它在渲染前运行,这有助于防止布局偏移或界面闪烁。

查看实际示例:不再有界面闪烁

如果你对 React 组件的生命周期不熟悉,可以查看我的文章 ✨3 分钟了解 React 生命周期 😉.

useLayoutEffect(() => {
  const height = ref.current.offsetHeight;
  setHeight(height);
}, []);

8. useSWR / useQuery

SWR 和 TanStack Query 正在成为 React 中获取数据的标准。

SWR 更简单,适用于大多数用例,而 TanStack Query 更复杂且强大。

SWR

const fetcher = (url) => fetch(url).then((res) => res.json());

const { data, error, isLoading } = useSWR("/api/user", fetcher);

TanStack Query

const getUser = () => fetch("/api/user").then((res) => res.json());

const { data, isLoading } = useQuery({
  queryKey: ["user"],
  queryFn: getUser,
});

9. useSelector, useDispatch 或其他状态钩子

有时候 React 的内置状态工具还不够用。

然后你会看到这些钩子依赖于状态库:

  • useSelector, useDispatch from react-redux
  • useAtom from Jotai
  • etc.

10. useNavigate

React Router 非常流行。

所以它的导航钩子也是这样:

const navigate = useNavigate();
navigate("/profile");

11. useBoolean

我最喜欢的钩子之一。

它让管理布尔状态非常简洁。

const { value: isOpen, setFalse, toggle } = useBoolean();

return (
  <>
    <button onClick={toggle}>
      {isOpen ? "Close form" : "Open form"}
    </button>
    <Dialog isOpen={isOpen} onClose={setFalse}>
      {/* Form content */}
    </Dialog>
  </>
);

12. useCopyToClipboard

非常适合"复制"按钮。

const [copiedText, copy] = useCopyToClipboard();

const handleCopy = () => {
  copy("Hello World").catch(err => {
    errorToast("Failed to copy!", err);
  });
};

return (
  <>
    <button onClick={handleCopy}>Copy</button>
    {copiedText && <span>Copied!</span>}
  </>
);

13. usePrevious

用来比较当前值和前一个值。

非常适合根据变化触发操作。

const prevCount = usePrevious(count);
const didIncrease = prevCount !== undefined && count > prevCount;

useEffect(() => {
  if (didIncrease) {
    console.log("Count increased!");
  }
}, [didIncrease]);

14. useDebounce

对请求进行防抖,避免不必要的 API 调用。

const [query, setQuery] = useState("");
const [debouncedQuery] = useDebounce(query, 500);

const { data } = useSWR(`/api/search?q=${debouncedQuery}`, fetcher);

return (
  <> 
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Search..."
    />
    {/* TODO: Show data */}
  </>
);

15. useMediaQuery

想根据屏幕尺寸渲染不同内容?这个钩子就是为你准备的。

const isMobile = useMediaQuery("(max-width: 768px)");

return <div>{isMobile ? "Mobile View" : "Desktop View"}</div>;

16. useResizeObserver

适用于响应式布局或像 react-window 这样的库。

const ref = useRef(null);
const { width = 0, height = 0 } = useResizeObserver({ ref });

return (
  <div ref={ref}>
    Width: {width}, Height: {height}
  </div>
);

17. useLocalStorage

轻松存储和同步 localStorage 中的数据。

const [theme, setTheme] = useLocalStorage("theme", "light");

return (
  <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
    Switch to {theme === "light" ? "dark" : "light"}
  </button>
);