为何搜索输入时useEffect重复执行?如何避免不必要重渲染?
问题:输入框搜索时useEffect为何每次都执行?
相关代码
Sidebar组件
import SearchInput from './searchInput' const Sidebar = () => { const { list } = useList() const [searchValue, setSearchValue] = useState<string>('') useEffect(() => { console.log('list :>> ', list) }, [list]) return ( <SearchInput searchValue={searchValue} setSearchValue={setSearchValue} /> ) } export default React.memo(Sidebar)
useList自定义Hook
export default function useList() { let { data } = useQuery(GET_LIST) const list = _.cloneDeep(data)?.menuTree?.filter((i) => i.parentId === null) || [] const otherMenus = data?.menuTree || [] return { list, otherMenus } }
SearchInput组件
const SearchInput = ({ searchValue, setSearchValue }) => { return ( <div> <Input value={searchValue} onChange={(e) => setSearchValue(e.target.value)} /> </div> ) } export default React.memo(SearchInput)
已尝试组件memo化和缓存useQuery返回的数据,但均无效,寻求解决方法。
原因分析
- 输入框输入时,
searchValue状态更新,触发Sidebar组件重渲染。React.memo只能阻止props变化导致的重渲染,无法阻止组件内部状态变化引发的重渲染,所以Sidebar每次都会重新执行。 - 核心问题在于
useListHook:每次组件重渲染时,list都是通过_.cloneDeep+filter生成的新数组,数组的引用地址每次都不一样。而useEffect通过引用比较判断依赖是否变化,所以每次重渲染都会触发useEffect执行。 - 即使useQuery返回的
data是稳定的,每次对其深拷贝并过滤都会生成新的数组引用,导致list永远是新值。
解决方案
1. 用useMemo缓存计算结果
修改useList Hook,使用useMemo包裹list和otherMenus的计算逻辑,确保只有当data变化时才重新生成数组,而非每次组件重渲染都创建新数组:
import { useMemo } from 'react'; export default function useList() { const { data } = useQuery(GET_LIST); const list = useMemo(() => { // 仅当data变化时,才重新执行深拷贝和过滤 return _.cloneDeep(data)?.menuTree?.filter((i) => i.parentId === null) || []; }, [data]); const otherMenus = useMemo(() => { return data?.menuTree || []; }, [data]); return { list, otherMenus }; }
2. 移除不必要的_.cloneDeep(可选)
如果useQuery返回的data是只读的,不会被后续逻辑修改,可去掉_.cloneDeep减少性能开销,再配合useMemo缓存过滤结果:
const list = useMemo(() => { return data?.menuTree?.filter((i) => i.parentId === null) || []; }, [data]);
补充说明
Sidebar的React.memo无法阻止自身状态变化引发的重渲染,但通过useMemo稳定list的引用后,即使Sidebar重渲染,list的引用地址不变,useEffect的依赖不会触发变化,也就不会重复执行了。
内容的提问来源于stack exchange,提问作者Mir Stephen
相关产品推荐
相关产品推荐

