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

React中Context Provider内SetInterval执行两次问题求助

问题:React全局Context中代码(含setInterval)执行两次,移除Strict Mode仍存在

我的React应用运行时,Global.js中的代码(包括setInterval)会执行两次,已移除Strict Mode但问题仍存在。我希望实现可被任意组件修改的全局变量,请求排查该问题的原因并提供解决方法。

代码示例

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {NameProvider} from './Global';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  
    <NameProvider>
    <App />
    </NameProvider>
  
);
reportWebVitals();

App.js

import logo from './logo.svg';
import './App.css';
import ComOne from './ComOne'
import ComTwo from './ComTwo';
function App() {

  return (
    <div className="App">
      <ComOne />
      <ComTwo />
    </div>
  );
}

export default App;

ComOne.js

import { useEffect } from 'react';
import {useName} from './Global'
const ComOne = () => {

      const {setName} = useName();
      useEffect(()=>{
            setName('ASD')
      },[])
      //console.log('XYZ')

      return (
            <div>COMONE</div>
      );
};

export default ComOne;

ComTwo.js

import React from 'react';
class ComTwo extends React.Component {

      render() {
            return (
                  <div>COMTWO</div>
            ) 
      }
}

export default ComTwo;

Global.js

import React, { useContext, useEffect, useState } from 'react';

// Step 1: Create a Context
const NameContext = React.createContext();

// Step 2: Create a Provider Component
export const NameProvider = ({ children }) => {
  let [name, setName] = useState('');

  let counter = 0;

  useEffect(()=>{
      setInterval(()=>{
            counter++;
            //console.log(counter);
      },3000);
  },[])
  console.log('ABCX')
  return (
    <NameContext.Provider value={{ name, setName}}>
      {children}
    </NameContext.Provider>
  );
};

// Step 3: Create a Custom Hook to Access the Context
export const useName = () => {
  return useContext(NameContext);
};

原因分析

  1. Provider组件重渲染导致函数体代码重复执行:
    NameProvider是函数组件,当ComOne中的useEffect调用setName('ASD')时,会触发NameProvider的name状态更新,导致组件重新渲染,因此函数体内的let counter = 0和console.log('ABCX')会执行两次。

  2. setInterval的闭包陷阱与逻辑错误:
    虽然useEffect因依赖为空数组只会执行一次,但counter是声明在组件函数体内的普通变量,每次组件重渲染都会重新初始化。setInterval的回调引用的是第一次渲染时的counter,后续重渲染的counter与它无关,导致counter++无法正确累计数值;同时未清理定时器,存在内存泄漏风险。


解决方法

1. 优化Provider重渲染,稳定Context值

使用useCallback和useMemo保证setName和Context的value引用稳定,避免子组件不必要的重渲染:

// 修改后的Global.js
import React, { useContext, useEffect, useState, useCallback, useMemo } from 'react';

const NameContext = React.createContext();

export const NameProvider = ({ children }) => {
  const [name, setName] = useState('');
  // 用useCallback包裹setName,保持引用稳定
  const stableSetName = useCallback((newName) => {
    setName(newName);
  }, []);

  // 用useState维护counter,避免闭包问题
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      // 函数式更新获取最新counter值
      setCounter(prevCounter => prevCounter + 1);
      // console.log(prevCounter + 1); // 如需打印,直接用更新后的值
    }, 3000);

    // 清理定时器,防止内存泄漏
    return () => clearInterval(intervalId);
  }, []);

  console.log('ABCX');
  // 用useMemo稳定value引用
  const contextValue = useMemo(() => ({
    name,
    setName: stableSetName
  }), [name, stableSetName]);

  return (
    <NameContext.Provider value={contextValue}>
      {children}
    </NameContext.Provider>
  );
};

export const useName = () => {
  return useContext(NameContext);
};

2. 避免不必要的状态更新(可选)

如果ComOne中的setName('ASD')是初始化逻辑,可以直接移到NameProvider的useState初始化中,减少一次状态更新:

// 在NameProvider中直接初始化name
const [name, setName] = useState('ASD');

这样ComOne的useEffect可以移除,避免触发NameProvider的二次渲染。

3. 确认无重复挂载Provider

检查项目中是否有其他地方重复渲染NameProvider(比如路由组件、嵌套组件中),确保全局只挂载一次Provider。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 12:24:51