React Storybook集成Googleapis失败,报错google未定义求助
解决Storybook中Google Maps API加载导致
google is not defined的问题 我之前在Storybook里调试依赖Google Maps的React组件时,也碰到过一模一样的问题——明明在preview-head.html里加了API脚本,组件还是报错说google未定义。这大概率是异步加载的脚本还没完成,组件就已经开始渲染执行了,或者脚本本身加载出了问题。下面是几个亲测有效的解决思路:
1. 组件内添加加载状态,等待Google对象就绪
最直接的方式是在组件里判断window.google是否存在,等它加载完成后再执行依赖代码:
import { useEffect, useState } from 'react'; const PlacesComponent = () => { const [isGoogleReady, setIsGoogleReady] = useState(false); useEffect(() => { // 轮询检查Google对象是否加载完成 const checkGoogleLoaded = () => { if (window.google) { setIsGoogleReady(true); } else { // 每隔100ms检查一次,直到加载完成 setTimeout(checkGoogleLoaded, 100); } }; checkGoogleLoaded(); // 清理定时器 return () => clearTimeout(checkGoogleLoaded); }, []); // 未加载完成时显示加载态 if (!isGoogleReady) { return <div>Loading Google Maps...</div>; } // 安全使用Google Maps API const placesService = new google.maps.places.PlacesService(document.createElement('div')); // ...你的业务逻辑 return <div>Places Component Content</div>; }; export default PlacesComponent;
2. 利用Google Maps的回调机制,精准监听加载完成
修改preview-head.html里的脚本,添加callback参数,让API加载完成后触发一个全局事件,组件监听这个事件再渲染:
第一步:更新.storybook/preview-head.html
<!-- 加入callback参数,指定加载完成后调用initGoogleMaps --> <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initGoogleMaps" async defer></script> <script> // 定义全局回调函数,触发自定义事件 window.initGoogleMaps = function() { window.dispatchEvent(new Event('googleMapsLoaded')); }; </script>
第二步:组件内监听自定义事件
useEffect(() => { const handleGoogleLoaded = () => setIsGoogleReady(true); // 如果脚本已经加载完成(比如刷新页面),直接设置状态 if (window.google) { setIsGoogleReady(true); } else { window.addEventListener('googleMapsLoaded', handleGoogleLoaded); } // 清理事件监听 return () => window.removeEventListener('googleMapsLoaded', handleGoogleLoaded); }, []);
3. 检查脚本加载是否真的成功
有时候问题出在脚本本身没加载成功,比如:
- API密钥无效或没有启用Places库权限
- 浏览器网络请求被拦截(比如广告插件)
- 脚本URL拼写错误
可以打开浏览器的开发者工具→网络面板,搜索maps.googleapis.com,看看请求的状态码是不是200。如果是403,那就是密钥或权限问题,去Google Cloud控制台检查配置。
4. 用Storybook装饰器统一处理加载逻辑
如果多个组件都依赖Google Maps,可以写一个Storybook装饰器,统一等待Google加载完成后再渲染组件:
import { useEffect, useState } from 'react'; import PlacesComponent from './PlacesComponent'; export default { title: 'Components/PlacesComponent', component: PlacesComponent, decorators: [ (Story) => { const [googleReady, setGoogleReady] = useState(false); useEffect(() => { const checkGoogle = () => { if (window.google) { setGoogleReady(true); } else { const timer = setTimeout(checkGoogle, 100); return () => clearTimeout(timer); } }; checkGoogle(); }, []); return googleReady ? <Story /> : <div>Loading Google Maps for preview...</div>; }, ], }; export const Default = {};
5. 组件内动态加载脚本(不依赖preview-head)
如果不想依赖Storybook的preview-head.html配置,可以让组件自己动态加载Google Maps脚本:
useEffect(() => { const script = document.createElement('script'); script.src = `https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places`; script.async = true; script.defer = true; // 脚本加载完成后更新状态 script.onload = () => setIsGoogleReady(true); // 加载失败时提示错误 script.onerror = () => console.error('Failed to load Google Maps API'); document.head.appendChild(script); // 组件卸载时移除脚本 return () => document.head.removeChild(script); }, []);
这些方法里,我个人常用的是第2种(回调+自定义事件),因为它能精准控制组件渲染时机,不会有多余的轮询。你可以根据自己的场景选最合适的方案~
内容的提问来源于stack exchange,提问作者Hawaijar
相关产品推荐
相关产品推荐

