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
相关产品推荐
相关产品推荐

