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

React18 StrictMode下useEffect触发dispatch导致组件重复渲染

问题根因

React 18 开发环境开启StrictMode时,会对首次挂载的组件自动执行一次「挂载→卸载→重新挂载」的检测流程,用来提前暴露副作用逻辑缺少清理、非幂等调用的问题。你遇到的双次渲染、PayPal按钮重复加载不是框架bug,是当前useEffect写法存在三个明显问题:

  • 逻辑执行顺序错误:effect顶部无前置判断就直接异步调用addPaypalScript,不管当前订单是否已支付、PayPal是否已加载,每次effect执行都会先插一次脚本
  • 清理逻辑缺失:现有清理函数只做了orderPayReset的dispatch,没有处理已插入的DOM脚本、未完成的异步请求,导致重挂载时副作用叠加
  • 依赖数组不全:order、success、orderId这些effect内用到的外部值全部没加入依赖,既会触发闭包陈旧问题,也会导致effect无意义重复执行
修复方案

按照下面的规则调整代码即可,不需要关闭StrictMode——这个检测逻辑仅在开发环境生效,生产构建后不会触发双执行,能帮你提前规避很多内存泄漏、状态不一致的问题:

  • 给PayPal脚本加唯一ID标识,插入前先判断是否已存在,避免重复插入DOM
  • 增加卸载标记,组件卸载后停止更新状态,避免内存泄漏
  • 补全清理逻辑:组件卸载时取消未完成的请求、移除已插入的PayPal脚本、重置加载状态
  • 补全useEffect依赖数组,调整执行顺序,删掉顶部无意义的立即执行函数
  • SDK加载完成前不要渲染PayPal按钮,避免组件重复初始化

修正后的完整代码:

const dispatch = useDispatch();
const [sdkReady, setSdkReady] = useState(false);

useEffect(() => {
  // 卸载标记,避免组件销毁后更新状态
  let isUnmounted = false;
  // PayPal脚本全局唯一ID,防止重复插入
  const PAYPAL_SCRIPT_ID = 'paypal-checkout-sdk';
  // 异步请求取消器
  const abortController = new AbortController();

  const addPaypalScript = async () => {
    // 已存在脚本直接标记为就绪,不重复加载
    const existScript = document.getElementById(PAYPAL_SCRIPT_ID);
    if (existScript) {
      if (window.paypal && !isUnmounted) setSdkReady(true);
      return;
    }

    try {
      const res = await axios.get('http://localhost:8000/api/config/paypal', {
        signal: abortController.signal
      });
      const clientId = res.data;
      // 并发场景二次校验,避免重复插入
      if (document.getElementById(PAYPAL_SCRIPT_ID)) return;

      const script = document.createElement('script');
      script.id = PAYPAL_SCRIPT_ID;
      script.type = 'text/javascript';
      script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}`;
      script.async = true;
      script.onload = () => {
        if (!isUnmounted) setSdkReady(true);
      };
      document.body.appendChild(script);
    } catch (err) {
      // 主动取消的请求不打印错误
      if (err.name !== 'CanceledError') console.error('PayPal SDK加载失败', err);
    }
  };

  // 按订单状态执行对应逻辑
  if (!order || success) {
    dispatch(orderDetail(orderId));
  } else if (!order.isPaid) {
    if (!window.paypal) {
      addPaypalScript();
    } else {
      setSdkReady(true);
    }
  }

  // 完整清理逻辑
  return () => {
    isUnmounted = true;
    abortController.abort();
    dispatch(orderPayReset());
    // 移除已插入的SDK脚本,保证重挂载时环境干净
    const loadedScript = document.getElementById(PAYPAL_SCRIPT_ID);
    if (loadedScript) loadedScript.remove();
    setSdkReady(false);
  };
}, [dispatch, order, success, orderId]);

return (
  sdkReady ? (
    <PayPalButton
      amount={order.totalPrice}
      onSuccess={successPaymentHandler}
    />
  ) : <div>支付组件加载中...</div>
)

补充说明:如果使用的是官方react-paypal-js之类的封装库,这类库本身已经做了幂等处理,只需要按照文档传入参数即可,不需要手动插入script标签。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 03:20:03