You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React自定义useHandleData Hook陷入无限循环问题求助

React自定义Hook useHandleData无限循环问题排查与解决

我创建了一个名为useHandleData的自定义React Hook,用于根据searchQuery、filters、searchKeys对传入的数据执行搜索和过滤,但Hook中的useEffect陷入了无限循环,相关代码如下:

Hook代码

import React, { useEffect, useState } from 'react';

type PropsType<T> = {
  searchQuery?: string;
  filters?: Array<{ filter: string, filterKey: keyof T }>;
  searchKeys?: (keyof T)[];
} & (
  { searchQuery: string, searchKeys: (keyof T)[] } |
  { filters: Array<{ filter: string, filterKey: keyof T }> }
)

const useHandleData = <T>(props: PropsType<T>, data: Array<T>) => {
  const { searchQuery, filters = [], searchKeys = [] } = props;
  const [displayData, setDisplayData] = useState<Array<T>>([]);

  useEffect(() => {
    let filteredData = data;
    if (searchQuery && searchKeys.length > 0) {
      filteredData = searchThroughData(filteredData, searchQuery, searchKeys);
    }
    if (filters.length > 0) {
      filters.forEach(filter => {
        filteredData = filterThroughData(filteredData, filter.filter, filter.filterKey);
      });
    }
    setDisplayData(filteredData);
  }, [searchQuery, filters, data]);

  return { displayData };
}

const searchThroughData = <T>(data: Array<T>, searchQuery: string, searchKeys: (keyof T)[]) => {
  if (!searchQuery || searchKeys.length === 0) return data;
  return data.filter(item => {
    for (let key of searchKeys) {
      if (item[key]?.toString().toLowerCase().includes(searchQuery.toString().toLowerCase())) return true;
    }
    return false;
  });
}

const filterThroughData = <T>(data: Array<T>, filter: string, filterKey: keyof T) => {
  if (!filter || !filterKey) return data;
  return data.filter(item => item[filterKey] === filter);
}

export { useHandleData };

调用代码

const [searchQuery, setSearchQuery] = useState("")
const [filterQuery, setFilterQuery] = useState<{ filter: string, filterKey: keyof item }[]>([])

const { displayData } = useHandleData({
  searchQuery: searchQuery,
  searchKeys: ['name', 'itemName'],
  filters: filterQuery
}, itemList)

问题原因

无限循环的核心是**useEffect依赖项的引用不稳定**,具体包括:

  • data参数:如果父组件传入的itemList不是通过useState或useMemo缓存的,每次组件渲染都会生成新的数组实例,导致data依赖项每次变化,触发useEffect重复执行。
  • searchKeys参数:调用Hook时直接传入['name', 'itemName'],每次渲染都会创建新数组,且useEffect遗漏了searchKeys依赖,导致逻辑依赖不稳定。
  • filters参数:如果父组件中filterQuery的更新或定义方式导致每次生成新数组,也会触发useEffect重复执行。
  • 状态更新循环:useEffect中调用setDisplayData会触发组件重新渲染,若依赖项持续变化,就会形成无限循环。

解决方案

1. 稳定data引用

在父组件中用useMemo缓存动态生成的itemList,确保其引用稳定:

// 父组件
const itemList = useMemo(() => {
  // 这里是生成数据的逻辑,比如从API获取或本地计算
  return [/* 你的数据数组 */];
}, []); // 根据实际依赖添加,比如API请求参数等

2. 稳定searchKeys引用

用useMemo缓存searchKeys,避免每次渲染创建新数组:

// 父组件
const searchKeys = useMemo(() => ['name', 'itemName'], []);

const { displayData } = useHandleData({
  searchQuery: searchQuery,
  searchKeys,
  filters: filterQuery
}, itemList);

3. 用useMemo替代useEffect+useState

直接通过useMemo计算过滤结果,省去额外的状态更新,更高效也避免循环:

const useHandleData = <T>(props: PropsType<T>, data: Array<T>) => {
  const { searchQuery, filters = [], searchKeys = [] } = props;

  const displayData = useMemo(() => {
    let filteredData = [...data]; // 可选:复制原数组避免直接修改
    if (searchQuery && searchKeys.length > 0) {
      filteredData = searchThroughData(filteredData, searchQuery, searchKeys);
    }
    if (filters.length > 0) {
      filters.forEach(filter => {
        filteredData = filterThroughData(filteredData, filter.filter, filter.filterKey);
      });
    }
    return filteredData;
  }, [searchQuery, filters, searchKeys, data]);

  return { displayData };
};

4. 确保filters引用稳定

更新filterQuery时,使用函数式更新确保仅在必要时创建新数组:

// 父组件中添加过滤条件的示例
const addFilter = (newFilter) => {
  setFilterQuery(prevFilters => [...prevFilters, newFilter]);
};

额外优化

如果filters中的对象是复杂类型,浅比较无法识别内容变化,可以自定义深比较的useMemo,或者使用第三方库(如use-deep-compare-effect)来避免不必要的重新计算。

内容的提问来源于stack exchange,提问作者Rohan P Suresh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 09:46:06