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

ReactJS中JSX添加外链script标签未加载资源的原因及正确写法

React中插入script标签不加载执行的原因与正确实现

问题场景

在ReactJS应用中尝试通过JSX在页面指定位置插入两个script组件,以下三种实现方式都能将script元素成功添加到DOM树的预期位置,但浏览器都不会发起script标签src属性对应资源的HTTP请求,相关脚本也不会执行:

实现方式1

return (
  <div
    dangerouslySetInnerHTML={{
      __html: `<script src='https://path.to/first/script.js'></script><script src='https://path.to/second/script.js'></script>`
    }}
  />
);

实现方式2

return (
  <div>
    <script src='https://path.to/first/script.js'></script>
    <script src='https://path.to/second/script.js'></script>
  </div>
);

实现方式3

import Safe from 'react-safe';
return (
  <div>
    <Safe.script src='https://path.to/first/script.js'></Safe.script>
    <Safe.script src='https://path.to/second/script.js'></Safe.script>
  </div>
);

问题1:为什么上述方式插入的script标签不会自动加载执行?

这是浏览器原生安全机制和React DOM更新逻辑共同导致的:

  • 浏览器层面的核心规则:只有HTML文档初始解析阶段插入的script标签,或者通过document.write写入的script标签,才会被浏览器自动识别、加载并执行。页面初始加载完成后,通过innerHTML、appendChild这类常规DOM API动态插入的script标签,浏览器默认不会触发其加载和执行逻辑,这是浏览器防止恶意脚本随意注入执行的基础安全防护设计。
  • React层面的渲染逻辑:不管是直接在JSX中写script标签,还是用dangerouslySetInnerHTML、第三方类库插入script,本质都是React完成首次渲染后,通过DOM操作把script节点追加到已存在的DOM树上,完全不属于页面初始解析阶段的插入行为,自然不会触发浏览器的script加载执行逻辑。
  • 与img等标签的表现差异:浏览器对img、link这类资源标签的加载规则没有上述限制,只要节点被插入DOM树,不管是初始解析阶段还是后续动态插入,都会自动触发src/href对应资源的请求,因此会出现和script行为不一致的情况。

问题2:React中正确插入并加载执行script标签的实现方式

核心思路是绕过React的渲染流程,手动通过原生DOM API创建script节点,配置好加载规则后插入到目标位置,常见可落地的实现有两种:

方式1:useEffect配合原生DOM操作(函数组件推荐)

在组件挂载完成后,手动创建script标签插入到目标容器,可按需处理加载成功、失败的回调,还能保证脚本执行顺序:

import { useEffect, useRef } from 'react';

export default function ScriptBlock() {
  const containerRef = useRef(null);

  useEffect(() => {
    const scriptList = [
      'https://path.to/first/script.js',
      'https://path.to/second/script.js'
    ];

    // 按顺序串行加载脚本,保证执行顺序和插入顺序一致
    const loadAllScripts = async () => {
      for (const src of scriptList) {
        await new Promise((resolve, reject) => {
          // 避免重复加载同一份脚本
          if (document.querySelector(`script[src="${src}"]`)) {
            resolve();
            return;
          }
          const scriptEl = document.createElement('script');
          scriptEl.src = src;
          scriptEl.async = false; // 关闭异步加载,保证执行顺序
          scriptEl.onload = resolve;
          scriptEl.onerror = reject;
          containerRef.current.appendChild(scriptEl);
        });
      }
      // 所有脚本加载执行完成后的自定义逻辑可写在此处
    };

    loadAllScripts();
  }, []);

  return <div ref={containerRef}></div>;
}

方式2:封装通用脚本加载方法

如果需要在多个业务场景动态加载脚本,可以封装通用加载函数,不依赖组件渲染逻辑:

/**
 * 动态加载外部脚本
 * @param {string} src 脚本地址
 * @param {Object} config 脚本配置项,支持async、defer等原生script属性
 * @param {HTMLElement} container 脚本插入的容器节点,默认插入body末尾
 * @returns {Promise} 脚本加载完成的Promise
 */
function loadExternalScript(src, config = {}, container = document.body) {
  return new Promise((resolve, reject) => {
    // 重复加载校验
    if (document.querySelector(`script[src="${src}"]`)) {
      resolve();
      return;
    }
    const scriptEl = document.createElement('script');
    scriptEl.src = src;
    Object.entries(config).forEach(([key, value]) => {
      scriptEl[key] = value;
    });
    scriptEl.onload = resolve;
    scriptEl.onerror = reject;
    container.appendChild(scriptEl);
  });
}

// 使用示例:按顺序加载两个脚本
loadExternalScript('https://path.to/first/script.js', { async: false })
  .then(() => loadExternalScript('https://path.to/second/script.js', { async: false }))
  .then(() => {
    // 脚本加载完成后的业务逻辑
  });

注意:如果加载的是第三方来源的不可信脚本,务必提前做好安全校验,避免引发XSS安全风险。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 07:18:23