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

如何避免React中Context Provider辅助函数引发的重复渲染?

问题

优化React Toast组件渲染性能时遇到以下问题:

  • 新增Toast时,所有现有Toast都会重新渲染
  • 删除某个Toast时,剩余Toast也会重新渲染

相关代码片段:

ToastProvider.jsx

function ToastProvider({ children }) {
  const [toasts, setToasts] = useState([])
  const memoisedClearToast = React.useCallback(() => setToasts([]), [])
  useKeyDown("Escape", memoisedClearToast)

  const addToast = (toast) => {
    setToasts([...toasts, toast]);
  };

  const dismissToast = (toastId) => {
    const nextToasts = [...toasts]
    const filtered = nextToasts.filter((toast) => toast.id !== toastId)
    setToasts(filtered)
  }

  const value = React.useCallback({ toasts, addToast, dismissToast }, [toasts])

  return (
    <ToastContext.Provider value={value}>
      {children}
    </ToastContext.Provider>
  )
}

ToastShelf.jsx

function ToastShelf() {
  const {toasts} = useContext(ToastContext)
  console.log(`toast shelf render`)
  return (
    <ol>
      {toasts.map((toast) => {
        return (
          <li key={toast.id}>
            <Toast
              id={toast.id}
              variant={toast.variant}
              message={toast.message}
            />
          </li>
        )
      })}
    </ol>
  );
}
export default React.memo(ToastShelf);

Toast.jsx

function Toast({ id, variant, message }) {
  const { dismissToast } = useContext(ToastContext)
  console.log(`toast render`)
  return (
    <div>
      <button onClick={() => dismissToast(id)}>
        <X size={24} />
      </button>
    </div>
  );
}
export default React.memo(Toast);

问题根源:addToast和dismissToast函数依赖toasts状态,每次toasts更新时这两个函数会生成新引用,导致Context的value对象更新,进而触发所有消费Context的Toast组件重渲染。

解决方案

1. 用函数式更新稳定函数引用

修改ToastProvider.jsx中的状态更新逻辑,使用React的函数式更新获取最新状态,让addToast和dismissToast不再依赖toasts:

const addToast = React.useCallback((toast) => {
  // 通过函数式更新获取最新的toasts数组
  setToasts(prevToasts => [...prevToasts, toast]);
}, []); // 空依赖数组,确保函数引用永久稳定

const dismissToast = React.useCallback((toastId) => {
  setToasts(prevToasts => prevToasts.filter(toast => toast.id !== toastId));
}, []); // 空依赖数组,函数引用稳定

2. 优化Context的value对象

使用useMemo创建Context的value对象,仅在toasts变化时更新(addToast和dismissToast引用已稳定,不会触发更新):

const value = React.useMemo(() => ({
  toasts,
  addToast,
  dismissToast
}), [toasts, addToast, dismissToast]);

修改后的完整ToastProvider.jsx

function ToastProvider({ children }) {
  const [toasts, setToasts] = useState([])
  const memoisedClearToast = React.useCallback(() => setToasts([]), [])
  useKeyDown("Escape", memoisedClearToast)

  const addToast = React.useCallback((toast) => {
    setToasts(prevToasts => [...prevToasts, toast]);
  }, []);

  const dismissToast = React.useCallback((toastId) => {
    setToasts(prevToasts => prevToasts.filter(toast => toast.id !== toastId));
  }, []);

  const value = React.useMemo(() => ({
    toasts,
    addToast,
    dismissToast
  }), [toasts, addToast, dismissToast]);

  return (
    <ToastContext.Provider value={value}>
      {children}
    </ToastContext.Provider>
  )
}

效果说明

  • ToastShelf仍会在toasts变化时重渲染(正常逻辑,因为需要渲染新的Toast列表)
  • 现有Toast组件的props(id、variant、message)未变化,且依赖的dismissToast引用稳定,React.memo会阻止不必要的重渲染
  • 新增或删除Toast时,仅目标Toast会触发渲染,其余Toast保持不变

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 11:53:10