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

React兄弟组件传值时如何避免不必要重渲染?

问题描述

尝试从子组件向兄弟组件更新状态时,所有组件持续重渲染。推测是状态更新需通过父组件传递导致整页重渲染。仅希望Child Component 1更新数据时,Child Component 2重渲染,但目前所有组件都在重渲染,还导致部分抽屉关闭,用户体验糟糕。已尝试用React Context解决但未成功,求优化建议。

最小复现代码如下:

React Context 创建文件

import React from "react";

interface RangeHighlightsContextProps {
  setRangeHighlights: (highlights: number[]) => void;
  rangeHighlights: number[];
}

export const RangeHighlightsContext = React.createContext<RangeHighlightsContextProps>({
  setRangeHighlights: () => {},
  rangeHighlights: [],
});

父组件

import { RangeHighlightsContext } from "./contexts/RangeHighlightContext";

const [rangeHighlights, setRangeHighlights] = useState<number[]>([]);

// ...其他代码

<RangeHighlightsContext.Provider value={{ setRangeHighlights, rangeHighlights }}>
  <ChildComponent1 />
  <ChildComponent2 />
</RangeHighlightsContext.Provider>

Child Component 1

function ChildComponent1(){
const { setRangeHighlights } = useContext(RangeHighlightsContext);

function handleRangeHighlightsChange(highlights: number[]) {
  setRangeHighlights(highlights);
}

  return(
  <div  onClick={(e) => {
          e.stopPropagation();
          handleRangeHighlightsChange([1,2,3]);
        }}>Click Here</div>
  )
}

Child Component 2

function ChildComponent2(){
  const newHighlights = useContext(RangeHighlightsContext);

  return(
    <div>{newHighlights.rangeHighlights}</div>
  )
}

子组件中抽屉状态的设置方式:

const [drawerState, setDrawerState] = useState("none");
const [drawerOpen, setDrawerOpen] = useState(false); // 补充缺失的状态定义

useEffect(() => {
  if (!drawerOpen) {
    setDrawerState("none");
  }
  if (drawerOpen) {
    console.log("opening");
    setDrawerState("block");
  }
}, [drawerOpen]);

function handleDrawer() {
  setDrawerOpen((drawerOpen) => !drawerOpen);
}

优化方案

1. 缓存Context Provider的value

父组件每次渲染时,都会创建新的{ setRangeHighlights, rangeHighlights }对象,导致Context Provider的value变化,所有消费该Context的组件都会重渲染。用useMemo缓存这个value,只有当rangeHighlights变化时才更新:

// 父组件中
const contextValue = useMemo(() => ({
  setRangeHighlights,
  rangeHighlights
}), [rangeHighlights]); // setRangeHighlights是useState返回的稳定函数,无需加入依赖

<RangeHighlightsContext.Provider value={contextValue}>
  <ChildComponent1 />
  <ChildComponent2 />
</RangeHighlightsContext.Provider>

2. 拆分Context(进一步优化)

如果只需要Child1调用更新函数、Child2读取状态,可以拆分两个Context:一个存状态,一个存更新函数。这样消费更新函数的组件(Child1)不会因为状态变化而重渲染:

拆分后的Context文件

import React from "react";

// 状态Context
export const RangeHighlightsStateContext = React.createContext<number[]>([]);
// 更新函数Context
export const RangeHighlightsDispatchContext = React.createContext<(highlights: number[]) => void>(() => {});

父组件修改

import { RangeHighlightsStateContext, RangeHighlightsDispatchContext } from "./contexts/RangeHighlightContext";

const [rangeHighlights, setRangeHighlights] = useState<number[]>([]);
// 缓存更新函数(可选,setRangeHighlights本身稳定)
const dispatch = useCallback(setRangeHighlights, []);

<RangeHighlightsStateContext.Provider value={rangeHighlights}>
  <RangeHighlightsDispatchContext.Provider value={dispatch}>
    <ChildComponent1 />
    <ChildComponent2 />
  </RangeHighlightsDispatchContext.Provider>
</RangeHighlightsStateContext.Provider>

Child1修改

function ChildComponent1(){
const setRangeHighlights = useContext(RangeHighlightsDispatchContext);

function handleRangeHighlightsChange(highlights: number[]) {
  setRangeHighlights(highlights);
}

return(
  <div onClick={(e) => {
          e.stopPropagation();
          handleRangeHighlightsChange([1,2,3]);
        }}>Click Here</div>
  )
}

Child2修改

function ChildComponent2(){
  const rangeHighlights = useContext(RangeHighlightsStateContext);

  return(
    <div>{rangeHighlights}</div>
  )
}

3. 用React.memo包裹不需要重渲染的组件

对于抽屉所在的组件(以及其他不需要随rangeHighlights变化的组件),用React.memo包裹,避免不必要的重渲染:

const DrawerComponent = React.memo(function DrawerComponent() {
  const [drawerState, setDrawerState] = useState("none");
  const [drawerOpen, setDrawerOpen] = useState(false);

  useEffect(() => {
    setDrawerState(drawerOpen ? "block" : "none");
  }, [drawerOpen]);

  function handleDrawer() {
    setDrawerOpen(prev => !prev);
  }

  return (
    // 抽屉JSX
  );
});

4. 优化抽屉状态逻辑(可选)

抽屉的drawerState可以直接由drawerOpen推导,无需额外状态和useEffect,减少不必要的更新:

const DrawerComponent = React.memo(function DrawerComponent() {
  const [drawerOpen, setDrawerOpen] = useState(false);

  function handleDrawer() {
    setDrawerOpen(prev => !prev);
  }

  return (
    <div style={{ display: drawerOpen ? "block" : "none" }}>
      {/* 抽屉内容 */}
    </div>
  );
});

内容的提问来源于stack exchange,提问作者deadant88

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:43:14