在服务端渲染时导入必须使用客户端window对象的高德地图组件+解决‘window is not defined’的报错

2,181 阅读3分钟

在一次实践过程中采用了next.js实现高德地图的组件。但是运行的时候报错window is not defined。这篇笔记介绍了一种动态导入的方法,它可以解决服务端渲染中出现不能使用window对象的问题。

概述

  1. 服务端渲染导入高德地图组件
  2. window is not defined报错以及解决方案
  3. 动态导入(Dynamic Import)
  4. 总结

服务端渲染导入高德地图组件

采用next.js导入高德地图组件。高德地图组件一开始引入之后需要初始化地图和创建初始化地图实例,初始化地图实例是高德地图组件中最重要的一步,也是后续功能开展的基础。然而该步骤需要获取只有客户端渲染才会有的window对象

高德地图给的参考代码如下

class MapComponent extends Component {
  constructor() {
    super();
    this.map = {};
  }

  // dom渲染成功后进行map对象的创建
  componentDidMount() {
    AMapLoader.load({
      key: '', // 申请好的Web端开发者Key,首次调用 load 时必填
      version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
      plugins: [''], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
    })
      .then(AMap => {
        this.map = new AMap.Map('container', {
          // 设置地图容器id
          viewMode: '3D', // 是否为3D地图模式
          zoom: 5, // 初始化地图级别
          center: [105.602725, 37.076636], // 初始化地图中心点位置
        });
      })
      .catch(e => {
        console.log(e);
      });
  }

  render() {
    // 初始化创建地图容器,div标签作为地图容器,同时为该div指定id属性;
    return <div id="container" className="map" style={{ height: '800px' }}></div>;
  }
}
// 导出地图组建类
export default MapComponent;

报错以及解决方案

运行改代码的时候报错:

Server Error ReferenceError: window is not defined

分析该报错的原因:

由于next.js是一个类似服务端渲染的框架,因此没有 `window` 对象。

所以在服务端渲染的程序中是不能直接使用window 对象的,因为它代表了浏览器窗口的全局对象,只能在客户端JavaScript 中使用。

但由于服务端渲染具备首屏加载速度快、利于SEO、利于维护与拓展等相关优点,以及与我们项目的匹配程度,所以依然考虑继续使用服务端渲染next.js框架。那么接下来就是如何解决 window is not defined报错了。

解决方案:动态引入(Dynamic Import)

在客户端加载时,存在window对象。我们在useEffect()中动态导入@amap/amap-jsapi-loader,只有在客户端执行时才执行该异步函数。就可以实现amap-jsapi-loader组件的导入。接着再 load() 方法来加载地图API,就可以获取当前的位置了。等到加载完成后,状态的 amapLoaded 被设置为 true

同时为了确保这段代码在客户端执行,使用typeof window 进行一个判断 typeof window !== "undefined" 条件的作用是仅在客户端渲染时执行相关代码才生效。

代码如下:

import { useState, useEffect } from 'react';

function MapComponent() {
  const [amapLoaded, setAmapLoaded] = useState(false)

  useEffect(() => {
    if (typeof window !== "undefined") {
      import('@amap/amap-jsapi-loader').then(AMapLoader => {
        AMapLoader.load({
          key: 'your amap key',
          version: '2.0',
        }).then(() => {
          setAmapLoaded(true)
        })
      })
    }
  }, [])

  return (
    <>
      {amapLoaded && <div className="map-container" />}
    </>
  );
}

export default MapComponent;

总结

在服务端渲染没有window属性的情况下,采用动态的导入的方法,导入高德地图组件amap-jsapi-loader组件。可以解决服务端渲染没有window属性的冲突,基于静态声明的模块导入 import 语句只能放置在文件的顶部。并且,import 要求传入的模块描述符必须是字符串字面量,不能包含变量。这些限制使得现在的动态导入访问远比静态声明的模块导入方案灵活。这一特性极大的提升网页应用的性能。