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

ReactJS+TypeScript:如何在Scripts组件加载完成后渲染后续组件

问题原因

React渲染<Scripts>组件时会生成<script>标签,但外部脚本是异步加载的——后续路由组件(比如UserEdit)渲染时,脚本可能还未加载完成,导致window.isJsonString不存在,触发TypeError。

以下是无需硬编码到index.html的解决方案,按场景分类:


方案一:全局脚本加载完成后再渲染应用

适合所有路由都依赖这些脚本的场景,确保应用初始化前脚本已就绪。

1. 改造Scripts组件,添加加载完成回调

import { useEffect } from 'react';

type ScriptsProps = {
  onLoaded: () => void;
};

const Scripts = ({ onLoaded }: ScriptsProps) => {
  const scriptList = ["frontend/js_clean/check.js", "frontend/js_clean/user.js"];

  useEffect(() => {
    let loadedCount = 0;
    const total = scriptList.length;

    const handleLoad = () => {
      loadedCount++;
      if (loadedCount === total) onLoaded();
    };

    scriptList.forEach(src => {
      const script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = `${process.env.ASSETS_CDN}/${src}`;
      script.onload = handleLoad;
      // 处理脚本加载失败的情况
      script.onerror = () => {
        console.error(`加载脚本失败:${src}`);
        loadedCount++;
        if (loadedCount === total) onLoaded();
      };
      document.body.appendChild(script);

      // 组件卸载时清理脚本
      return () => document.body.removeChild(script);
    });
  }, [scriptList, onLoaded]);

  return null;
};

export default Scripts;

2. 根组件等待脚本加载完成后渲染路由

import { useState } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Scripts from './Scripts';
import PageWrapper from './PageWrapper';
import UserEdit from './UserEdit';
import UserPage from './UserPage';
import NoPage from './NoPage';

const root = document.getElementById('root');
if (root) {
  const RootApp = () => {
    const [scriptsReady, setScriptsReady] = useState(false);

    return (
      <>
        <Scripts onLoaded={() => setScriptsReady(true)} />
        {scriptsReady && (
          <BrowserRouter>
            <Routes>
              <Route path="/" element={<PageWrapper />}>
                <Route index element={<UserEdit />} />
                <Route path="user/edit" element={<UserEdit />} />
                <Route path="user/:id" element={<UserPage />} />
                <Route path="*" element={<NoPage />} />
              </Route>
            </Routes>
          </BrowserRouter>
        )}
      </>
    );
  };

  createRoot(root).render(<RootApp />);
}

方案二:特定组件内按需加载脚本

适合只有个别组件依赖脚本的场景,减少应用初始加载体积。

import { useState, useEffect } from 'react';

const UserEdit = () => {
  const [isScriptReady, setIsScriptReady] = useState(false);

  useEffect(() => {
    // 先检查脚本是否已加载(避免重复加载)
    if (window.isJsonString) {
      setIsScriptReady(true);
      return;
    }

    const script = document.createElement('script');
    script.src = `${process.env.ASSETS_CDN}/frontend/js_clean/check.js`;
    script.onload = () => setIsScriptReady(true);
    script.onerror = () => console.error('加载check.js失败');
    document.body.appendChild(script);

    // 组件卸载时清理脚本
    return () => document.body.removeChild(script);
  }, []);

  // 脚本未加载完成时显示加载状态
  if (!isScriptReady) {
    return <div>加载中...</div>;
  }

  // 现在可以安全使用window.isJsonString
  const testJson = () => {
    const result = window.isJsonString('{"name":"test"}');
    console.log('是否为JSON字符串:', result);
  };

  return (
    <div>
      <button onClick={testJson}>测试JSON校验</button>
      {/* 组件其他内容 */}
    </div>
  );
};

export default UserEdit;

方案三:封装自定义Hook复用加载逻辑

如果多个组件需要加载脚本,可封装Hook提高复用性:

import { useState, useEffect } from 'react';

// 自定义Hook:加载单个脚本
const useScript = (src: string): boolean => {
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    // 检查脚本是否已存在
    const existingScript = document.querySelector(`script[src="${src}"]`);
    if (existingScript) {
      setLoaded(true);
      return;
    }

    const script = document.createElement('script');
    script.src = src;
    script.onload = () => setLoaded(true);
    script.onerror = () => console.error(`加载脚本失败:${src}`);
    document.body.appendChild(script);

    return () => script.parentNode?.removeChild(script);
  }, [src]);

  return loaded;
};

// 组件中使用示例
const UserEdit = () => {
  const checkScriptLoaded = useScript(`${process.env.ASSETS_CDN}/frontend/js_clean/check.js`);
  const userScriptLoaded = useScript(`${process.env.ASSETS_CDN}/frontend/js_clean/user.js`);
  const allReady = checkScriptLoaded && userScriptLoaded;

  if (!allReady) return <div>加载中...</div>;

  // 组件逻辑
  return <div>用户编辑页面</div>;
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 20:50:16