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

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;

问题根源

  1. 无限重渲染原因:filteredAllFaq是组件每次渲染时重新执行map生成的新数组(引用地址每次都变),且被加入useEffect的依赖数组。useEffect触发setDisplayList导致组件重渲染,重渲染又生成新的filteredAllFaq,再次触发useEffect,形成循环。
  2. 多次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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 04:10:38