React中useMemo解决无限重渲染,但用法存疑及渲染次数异常
React组件无限重渲染问题分析与优化
问题背景
编译正常但组件无限重渲染,控制台抛出警告:
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
原代码如下:
import { Box, Flex } from "@chakra-ui/react"; import { useEffect, useState } from "react"; import Section, { SectionHeading } from "../../components/UI/Section"; import FaqList from "../../data/FaqList"; import FAQFilterBtn from "./FAQFilterBtn"; import FAQItems from "./FAQItems"; const allFaqItems = Object.values(FaqList).map((elem) => elem.content); const allFaq = allFaqItems.map((elem, index) => ( <Box key={index}> <FAQItems items={elem} faqHeading={Object.values(FaqList)[index].heading} /> </Box> )); const FAQSection = () => { const [displayList, setDisplayList] = useState(allFaq); const [activeFilter, setActiveFilter] = useState("All"); const filteredAllFaq = allFaqItems.map((elem, index) => { const faq = activeFilter === Object.values(FaqList)[index].heading || activeFilter === "All" ? ( elem.length ? ( <FAQItems items={elem} faqHeading={Object.values(FaqList)[index].heading} /> ) : ( "" ) ) : ( "" ); return <Box key={index}>{faq}</Box>; }); const changeFilter = (filter: string) => { setActiveFilter(filter); }; useEffect(() => { setDisplayList(filteredAllFaq); }, [activeFilter, filteredAllFaq]); console.log(activeFilter); return ( <Section id="#faq-section"> <> <Flex w="full" justify="space-between"> <SectionHeading mb={0}>Frequently asked questions</SectionHeading> </Flex> <Flex m={4} mb={-4} gap={6}> <FAQFilterBtn name="All" active={activeFilter} setFilter={changeFilter} /> {FaqList.map((e, index) => ( <FAQFilterBtn key={index} name={e.heading} active={activeFilter} setFilter={changeFilter} /> ))} </Flex> {displayList} </> </Section> ); }; export default FAQSection;
问题根源
- 无限重渲染原因:
filteredAllFaq是组件每次渲染时重新执行map生成的新数组(引用地址每次都变),且被加入useEffect的依赖数组。useEffect触发setDisplayList导致组件重渲染,重渲染又生成新的filteredAllFaq,再次触发useEffect,形成循环。 - 多次console.log原因:开发环境下React严格模式会执行两次渲染(挂载和更新各两次),用于检测副作用问题,生产环境会恢复正常单次执行。
优化方案
1. 移除冗余状态(核心优化)
根本不需要把JSX元素存在状态里,直接在渲染阶段根据activeFilter计算即可,完全省去useEffect和displayList状态:
import { Box, Flex } from "@chakra-ui/react"; import { useState } from "react"; import Section, { SectionHeading } from "../../components/UI/Section"; import FaqList from "../../data/FaqList"; import FAQFilterBtn from "./FAQFilterBtn"; import FAQItems from "./FAQItems"; // 提前预处理FaqList,避免组件内重复调用Object.values const faqEntries = Object.values(FaqList); const allFaqItems = faqEntries.map((elem) => elem.content); const FAQSection = () => { const [activeFilter, setActiveFilter] = useState("All"); const changeFilter = (filter: string) => { setActiveFilter(filter); }; // 直接在渲染时计算展示内容,无需存状态 const displayList = faqEntries.map((entry, index) => { const shouldShow = activeFilter === entry.heading || activeFilter === "All"; if (!shouldShow) return null; if (!allFaqItems[index].length) return null; return ( <Box key={entry.heading}> {/* 用唯一的heading做key,比index更稳定 */} <FAQItems items={allFaqItems[index]} faqHeading={entry.heading} /> </Box> ); }); console.log(activeFilter); return ( <Section id="#faq-section"> <> <Flex w="full" justify="space-between"> <SectionHeading mb={0}>Frequently asked questions</SectionHeading> </Flex> <Flex m={4} mb={-4} gap={6}> <FAQFilterBtn name="All" active={activeFilter} setFilter={changeFilter} /> {faqEntries.map((entry) => ( <FAQFilterBtn key={entry.heading} {/* 用唯一heading做key */} name={entry.heading} active={activeFilter} setFilter={changeFilter} /> ))} </Flex> {displayList} </> </Section> ); }; export default FAQSection;
2. 可选:缓存JSX提升性能
如果faqEntries数据量极大,可使用useMemo缓存displayList,仅在activeFilter变化时重新计算:
import { useMemo } from "react"; // ...其他代码不变 const displayList = useMemo(() => { return faqEntries.map((entry, index) => { const shouldShow = activeFilter === entry.heading || activeFilter === "All"; if (!shouldShow) return null; if (!allFaqItems[index].length) return null; return ( <Box key={entry.heading}> <FAQItems items={allFaqItems[index]} faqHeading={entry.heading} /> </Box> ); }); }, [activeFilter]);
3. 关于多次console.log的说明
开发环境下的两次渲染是React严格模式的正常行为,用于检测组件中是否存在不安全的副作用,生产环境中只会执行一次,无需额外处理。
内容的提问来源于stack exchange,提问作者watsum08
相关产品推荐
相关产品推荐

