Skip to content
 

React useEffect 中函数依赖管理:使用函数引用作为依赖项

更新: 1/4/2026字数: 0 字 时长: 0 分钟

前言

React 函数组件中,我们经常会看到这样的代码:

javascript
/** 每一个都是一个函数,用于获取不同的列表数据 */
function fetchInstitutionList() {
  console.log("Fetching institution list...");
}

function fetchProcessStateList() {
  console.log("Fetching process state list...");
}

function fetchDeliveryTargetList() {
  console.log("Fetching delivery target list...");
}

function fetchDeliveryTypeList() {
  console.log("Fetching delivery type list...");
}

useEffect(() => {
  fetchInstitutionList();
  fetchProcessStateList();
  fetchDeliveryTargetList();
  fetchDeliveryTypeList();
}, [fetchInstitutionList, fetchProcessStateList, fetchDeliveryTargetList, fetchDeliveryTypeList]);

初看之下,这种写法似乎有些奇怪:为什么要把函数本身作为依赖项?这背后隐藏着 React Hooks 的重要设计哲学和最佳实践。

一、函数引用的陷阱

1.1 函数在每次渲染中都不同

JavaScript 中,函数声明在每次渲染时都会创建一个新的函数引用

javascript
function MyComponent() {
  // 每次渲染都创建新的函数
  const fetchData = () => {
    console.log("Fetching data...");
  };

  // ❌ 这会导致无限循环!
  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return <div>Component</div>;
}

每次组件渲染,fetchData 都是一个新的函数,导致 useEffect 依赖变化,重新执行,触发重新渲染...形成无限循环。

1.2 解决方案:useCallback

为了解决这个问题,React 提供了 useCallback Hook:

简单说:useCallback 能记住一个函数,只有当依赖变化时才创建新的函数,否则返回缓存的旧函数。
核心作用:优化性能,特别当函数作为 prop 传递给子组件时。

javascript
const fetchData = useCallback(() => {
  console.log("Fetching data...");
  // 这里可以使用 count 等状态
}, []); // ✅ 依赖数组决定何时创建新函数

useEffect(() => {
  fetchData();
}, [fetchData]); // ✅ 现在依赖是稳定的

二、实战场景分析

2.1 场景一:数据初始化(最常见的用例)

javascript
// 使用 useCallback 稳定函数引用
const fetchInstitutionList = useCallback(async () => {
  const result = await api.getInstitutionList();
  setInstitutionList(result);
}, []); // 空依赖:函数只创建一次

const fetchProcessStateList = useCallback(async () => {
  const result = await api.getProcessStateList();
  setProcessStateList(result);
}, []);

// 正确的写法
useEffect(() => {
  fetchInstitutionList();
  fetchProcessStateList();
  fetchDeliveryTargetList();
  fetchDeliveryTypeList();
}, [fetchInstitutionList, fetchProcessStateList, fetchDeliveryTargetList, fetchDeliveryTypeList]); // ✅ 依赖稳定,只会执行一次

2.2 场景二:带参数的动态获取

javascript
const [filters, setFilters] = useState({});

// 依赖 filters,当 filters 变化时创建新函数
const fetchFilteredData = useCallback(async () => {
  const result = await api.getData(filters);
  setData(result);
}, [filters]); // ✅ 明确依赖

useEffect(() => {
  fetchFilteredData();
}, [fetchFilteredData]); // ✅ filters 变化时重新获取

2.3 场景三:条件执行

javascript
const [shouldFetch, setShouldFetch] = useState(false);

const fetchData = useCallback(async () => {
  if (!shouldFetch) return;
  const result = await api.getData();
  setData(result);
}, [shouldFetch]); // ✅ 依赖 shouldFetch

useEffect(() => {
  fetchData();
}, [fetchData]); // ✅ shouldFetch 变化时重新评估

三、常见陷阱与解决方案

3.1 陷阱:依赖过多导致频繁执行

javascript
// ❌ 问题:任何依赖变化都导致重新执行
const fetchData = useCallback(() => {
  // ... 复杂的逻辑
}, [dep1, dep2, dep3, dep4, dep5]);

// ✅ 解决方案:分离关注点
const fetchCoreData = useCallback(() => {
  // 只依赖必要的状态
}, [essentialDep]);

const fetchAdditionalData = useCallback(() => {
  // 依赖其他状态
}, [otherDep]);

3.2 陷阱:不必要的依赖

javascript
// ❌ 不必要的依赖
const fetchData = useCallback(() => {
  // 实际不使用 count
  console.log("fetching");
}, [count]); // 不需要 count

// ✅ 正确的依赖
const fetchData = useCallback(() => {
  console.log("fetching");
}, []); // 空依赖

3.3 高级技巧:使用 useRef

javascript
// 当确实无法避免函数变化,但又不想触发 useEffect
const fetchDataRef = useRef();

useEffect(() => {
  fetchDataRef.current = async () => {
    // 可以使用最新的状态
    console.log("Fetching with latest state");
  };
});

useEffect(() => {
  const fetchLatest = async () => {
    await fetchDataRef.current?.();
  };
  fetchLatest();
}, []); // 空依赖,只执行一次

四、最佳实践总结

4.1 黄金法则

  1. 诚实声明依赖:所有在 useEffect 中使用的值都应在依赖数组中声明
  2. 稳定函数引用:使用 useCallback 包装函数,避免不必要的重新创建
  3. 最小化依赖:只依赖真正需要变化时重新执行的值

4.2 代码审查清单

4.3 性能优化建议

javascript
// 优化前:一个大的 useEffect
useEffect(() => {
  fetchA();
  fetchB();
  fetchC();
}, [fetchA, fetchB, fetchC]);

// 优化后:分离关注点
useEffect(() => {
  fetchA();
}, [fetchA]);

useEffect(() => {
  fetchB();
}, [fetchB]);

useEffect(() => {
  fetchC();
}, [fetchC]);

五、回到最初的问题

现在我们可以理解开篇代码的智慧:

javascript
useEffect(() => {
  fetchInstitutionList();
  fetchProcessStateList();
  fetchDeliveryTargetList();
  fetchDeliveryTypeList();
}, [fetchInstitutionList, fetchProcessStateList, fetchDeliveryTargetList, fetchDeliveryTypeList]);

这种写法是:

  1. 符合 React 规则:诚实地声明了所有依赖
  2. 性能友好:假设这些函数都用 useCallback 包装且依赖合理
  3. 可维护:明确显示了副作用的依赖关系
  4. 安全:避免了过时闭包问题

我见青山多妩媚,料青山见我应如是