HOW - 缓存 React 自定义 hook 的所有返回值(包括函数)

目录

场景

  • 如果你写了一个自定义 Hook,比如 useMyHook(),它暴露出某些值或函数给外部组件使用。
  • 那么你要特别注意 这些值或函数是否在每次渲染时都保持稳定 ,否则使用这些值的组件会频繁地重新渲染,或者造成外部 useEffectuseCallbackuseMemo 等 Hook 的依赖不准确,出现难以追踪的 bug。

优化方案

  • 如果你的 Hook 返回的是回一个值,你应该使用 useMemo 或者 useRef 将它缓存。
  • 如果你的 Hook 返回的是一个回调函数(如 handleChangeonClick),你应该使用 useCallback 将它缓存。

即,尽量使用 React 已提供的稳定引用,比如 useMemo, useCallback, useRef, 而不是每次都创建新值。这样能我们提供的 Hook 更高效、更可控。

示例

tsx 复制代码
function useCounter() {
  const [count, setCount] = useState(0);

  // ⚠️ 不推荐:每次调用 useCounter 都返回新函数
  // const increment = () => setCount(count + 1);

  // ✅ 推荐:使用 useCallback 缓存回调
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return { count, increment };
}

延伸例子:为什么这很重要?

tsx 复制代码
function useTodos() {
  const [todos, setTodos] = useState([]);

  // 每次渲染都返回新引用
  const addTodo = (text) => setTodos([...todos, { text }]);

  return { todos, addTodo };
}

如果外部组件用 useEffect(() => { ... }, [addTodo]),那它就会在每次渲染时重新执行,因为 addTodo 函数每次都是新引用。

改进后👇:

tsx 复制代码
const addTodo = useCallback(
  (text) => setTodos(todos => [...todos, { text }]),
  []
);

常见的请求 hook 封装

优化前

typescript 复制代码
const useLoadData = (fetchData) => {
  const [result, setResult] = useState({
    type: "pending",
  });

  const loadData = async () => {
    setResult({ type: "loading" });
    try {
      const data = await fetchData();
      setResult({ type: "loaded", data });
    } catch (err) {
      setResult({ type: "error", error: err });
    }
  };

  return { result, loadData };
};

优化后

下面是一个更高性能、语义更清晰且适合共享的版本,优化点包括:

优化点一览

  1. 使用 useCallback 缓存 loadData,防止外部组件因引用变化而频繁 re-render。
  2. 支持可选的自动加载(autoLoad)或依赖加载(传入依赖数组)。
  3. 支持外部传入初始状态和重置逻辑。
  4. 使用 useRef 存储 isMounted 防止组件卸载后状态更新(可选安全处理)。

优化后的 useLoadData

typescript 复制代码
import { useCallback, useEffect, useRef, useState } from "react";

type LoadState<T> =
  | { type: "pending" }
  | { type: "loading" }
  | { type: "loaded"; data: T }
  | { type: "error"; error: unknown };

interface UseLoadDataOptions {
  autoLoad?: boolean;
  deps?: any[];
}

function useLoadData<T>(
  fetchData: () => Promise<T>,
  options: UseLoadDataOptions = {}
) {
  const { autoLoad = false, deps = [] } = options;

  const [result, setResult] = useState<LoadState<T>>({ type: "pending" });

  const isMounted = useRef(true);
  useEffect(() => {
    return () => {
      isMounted.current = false;
    };
  }, []);

  const loadData = useCallback(async () => {
    setResult({ type: "loading" });
    try {
      const data = await fetchData();
      if (isMounted.current) {
        setResult({ type: "loaded", data });
      }
    } catch (err) {
      if (isMounted.current) {
        setResult({ type: "error", error: err });
      }
    }
  }, [fetchData]);

  // 自动加载逻辑
  useEffect(() => {
    if (autoLoad) {
      loadData();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, deps);

  return { result, loadData };
}

使用方式示例:

typescript 复制代码
const fetchUsers = () => fetch("/api/users").then(res => res.json());

const { result, loadData } = useLoadData(fetchUsers, {
  autoLoad: true,
  deps: [], // 你可以传入 [userId] 等依赖
});

if (result.type === "loading") return <p>加载中...</p>;
if (result.type === "error") return <p>出错啦:{String(result.error)}</p>;
if (result.type === "loaded") return <UserList users={result.data} />;

优点回顾

优化点 好处
useCallback 缓存 外部组件可稳定依赖 loadData,避免不必要副作用
自动加载支持 更通用,可在多场景下使用
isMounted 防护 防止异步请求完成后组件已经卸载导致的警告或错误
泛型支持 T 类型更友好,适用于各种返回值类型的数据加载
相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
全栈弄潮儿²⁰²⁴2 天前
AI Agent 开发实战(30):限流、缓存与成本控制
人工智能·gpt·缓存·agent·限流·agi·成本控制