带有 next.js 的传单?

Mic*_*orn 13 javascript leaflet react-leaflet next.js

我收到一个 ReferenceError:

将 next.js 与 Leaflet.js 一起使用时未定义 window 。

想知道这个问题是否有一个简单的解决方案 - 使用 next.js 是否使我的工作流程过于复杂?

对于那些对确切代码感到好奇的人,

import React, { createRef, Component } from "react";
import L from "leaflet";
import { Map, TileLayer, Marker, Popup, DivOverlay } from "react-leaflet";
import axios from "axios";
import Header from "./Header";


export default class PDXMap extends Component {
  state = {
    hasLocation: false,
    latlng: {
      lat: 45.5127,
      lng: -122.679565
    },
    geoJSON: null
  };

  mapRef = createRef();

  componentDidMount() {
    this.addLegend();
    if (!this.state.hasLocation) {
      this.mapRef.current.leafletElement.locate({
        setView: true
      });
    }
    axios
      .get(
        "https://opendata.arcgis.com/datasets/40151125cedd49f09d211b48bb33f081_183.geojson"
      )
      .then(data => {
        const geoJSONData = data.data;
        this.setState({ geoJSON: geoJSONData });
        return L.geoJSON(this.state.geoJSON).addTo(
          this.mapRef.current.leafletElement
        );
      });
  }

  handleClick = () => {
    this.mapRef.current.leafletElement.locate();
  };

  handleLocationFound = e => {
    console.log(e);
    this.setState({
      hasLocation: true,
      latlng: e.latlng
    });
  };

  getGeoJsonStyle = (feature, layer) => {
    return {
      color: "#006400",
      weight: 10,
      opacity: 0.5
    };
  };

  addLegend = () => {
    const map = this.mapRef.current.leafletElement;
    L.Control.Watermark = L.Control.extend({
      onAdd: function(map) {
        var img = L.DomUtil.create("img");

        img.src = "https://leafletjs.com/docs/images/logo.png";
        img.style.width = "200px";

        return img;
      }
    });

    L.control.watermark = function(opts) {
      return new L.Control.Watermark(opts);
    };

    L.control.watermark({ position: "bottomleft" }).addTo(map);
  };

  render() {
    const marker = this.state.hasLocation ? (
      <Marker position={this.state.latlng}>
        <Popup>
          <span>You are here</span>
        </Popup>
      </Marker>
    ) : null;

    return (
      <Map
        className="map-element"
        center={this.state.latlng}
        length={4}
        onClick={this.handleClick}
        setView={true}
        onLocationfound={this.handleLocationFound}
        ref={this.mapRef}
        zoom={14}
      >
        <TileLayer
          attribution='&amp;copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        {marker}
      </Map>
    );
  }
}

/**
 * TODO:  Add Header + Legend to map
 *        - Header to be styled
 *        - Legend to be present in header
 *
 */


import React from 'react';
import PDXMap from "../components/map";


export default function SignIn() {
  const classes = useStyles();

  return (
      <PDXMap/>
);
}
Run Code Online (Sandbox Code Playgroud)

我很乐意使用任何前进的方式 - 只是对获得功能性产品感兴趣。

干杯!

更新

嘿大家,

我仍然遇到这个错误(比我计划的要晚一点,哈哈)。

我目前正在使用这种方法和 useEffects,

import React, {useEffect, useState} from 'react';

function RenderCompleted() {

    const [mounted, setMounted] = useState(false);

    useEffect(() => {
        setMounted(true)

        return () => {
            setMounted(false)
        }
    });

    return mounted;
}

export default RenderCompleted;
Run Code Online (Sandbox Code Playgroud)

这是它显示的页面

import React, { useEffect } from "react";
import Router, { useRouter } from "next/router";
import { useRef, useState } from "react";


//viz
import PDXMap from "../../components/Visualization/GIS/map";

import RenderCompleted from "../../components/utils/utils";

// import fetch from 'isomorphic-unfetch';
import { Cookies, CookiesProvider } from "react-cookie";
const cookies = new Cookies();
//containers

// Layouts
import Layout from "../../components/Layout/Layout_example";
import Chart from "../../components/Visualization/Graphs/Chart";
import Table from "../../components/Visualization/Tables/Table";
import Sidebar from "../../components/Layout/Sidebar/SidebarProperty";



export default function Bargains() {

  // const [inbrowser, setBrowser] = useState(false);

  const choiceRef = useRef<any>();
  const [message, setMessage] = useState<any>(null);

  const [productList, setProductList] = useState<any>([]);
  const [searched, setSearched] = useState(false);

  const router = useRouter();

  let token = cookies.get("token");

  // useEffect(() => {
  //   setBrowser(true);
  // });
  const isMounted = RenderCompleted();


  const columns = React.useMemo(
    () => [
    ....
    ],

    []
  )



  async function handleChoice() {

    console.log("searching...", choiceRef.current?.value);
    setMessage("Searching...");
    var headers = {
      "Content-Type": "application/x-www-form-urlencoded",
      "auth-token": token,
    };

    fetch(
    ....
  }


            <div className="flex flex-wrap ">
            {isMounted && <PDXMap/>}


              
              <Table columns={columns as any} data={productList as any} />


            </div>
          </div>



        </div>
      </div>


    </Layout>



  )
}
Run Code Online (Sandbox Code Playgroud)

与相同的错误消息

ReferenceError: window is not defined
Run Code Online (Sandbox Code Playgroud)

##更新两个

好吧,很奇怪,当我从另一个页面浏览到站点时它确实有效,但当我加载页面本身时却无效。

会对此有一个想法,但也许是因为地图正在使用 componentDidMount() 加载数据并且交互异常?

更新

好的,我基于https://github.com/rajeshdh/react-leaflet-with-nextjs创建了一个更简单的示例

现在正在加载,但图块显示不正确,有些图块未加载。

这是我用来简单的地图组件,

import React, { Component, createRef } from 'react';
import { Map, TileLayer, Marker, Popup, MapControl, withLeaflet } from 'react-leaflet';
import { GeoSearchControl, OpenStreetMapProvider } from 'leaflet-geosearch';


class SearchBox extends MapControl {
  constructor(props) {
    super(props);
    props.leaflet.map.on('geosearch/showlocation', (e) => props.updateMarker(e));
  }

  createLeafletElement() {
    const searchEl = GeoSearchControl({
      provider: new OpenStreetMapProvider(),
      style: 'bar',
      showMarker: true,
      showPopup: false,
      autoClose: true,
      retainZoomLevel: false,
      animateZoom: true,
      keepResult: false,
      searchLabel: 'search'
    });
    return searchEl;
  }
}


export default class MyMap extends Component {
  state = {
    center: {
      lat: 31.698956,
      lng: 76.732407,
    },
    marker: {
      lat: 31.698956,
      lng: 76.732407,
    },
    zoom: 13,
    draggable: true,
  }

  refmarker = createRef(this.state.marker)

  toggleDraggable = () => {
    this.setState({ draggable: !this.state.draggable });
  }

  updateMarker = (e) => {
    // const marker = e.marker;
    this.setState({
      marker: e.marker.getLatLng(),
    });
    console.log(e.marker.getLatLng());
  }

  updatePosition = () => {
    const marker = this.refmarker.current;
    if (marker != null) {
      this.setState({
        marker: marker.leafletElement.getLatLng(),
      });
    }
    console.log(marker.leafletElement.getLatLng());
  }

  render() {
    const position = [this.state.center.lat, this.state.center.lng];
    const markerPosition = [this.state.marker.lat, this.state.marker.lng];
    const SearchBar = withLeaflet(SearchBox);

    return (
      <div className="map-root">
        <Map center={position} zoom={this.state.zoom} style={{
                        height:"700px"
                    }}>
          <TileLayer
            attribution='&amp;copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          />
          <Marker
            draggable={true}
            onDragend={this.updatePosition}
            position={markerPosition}
            animate={true}
            ref={this.refmarker}>
            <Popup minWidth={90}>
              <span onClick={this.toggleDraggable}>
                {this.state.draggable ? 'DRAG MARKER' : 'MARKER FIXED'}
              </span>
            </Popup>
          </Marker>
          <SearchBar updateMarker={this.updateMarker} />
        </Map>
        <style jsx>{`
                .map-root {
                  height: 100%;
                }
                .leaflet-container {
                 height: 400px !important;
                 width: 80%;
                 margin: 0 auto;
               }
           `}
        </style>
      </div>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

并称之为我正在使用这个,

const SimpleExample = dynamic(() => import("../../components/Visualization/GIS/map"), {
  ssr: false
}); 
Run Code Online (Sandbox Code Playgroud)

并尝试过这个 {isMounted && }

Fli*_*ary 35

2020年的答案

我也有这个问题并在我自己的项目中解决了它,所以我想我会分享我所做的。

NextJS 可以动态加载库并限制该事件,因此它不会在服务器端渲染期间发生。有关更多详细信息,请参阅文档

在下面的示例中,我将使用和修改来自 NextJS 10.0 和 React-Leaflet 3.0 文档网站的示例代码。

旁注:如果您使用 TypeScript,请确保安装@types/leaflet,否则您将在centerattribution属性上遇到编译错误。

首先,我将react-leaflet代码拆分为一个单独的组件文件,如下所示:

import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'

const Map = () => {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13} scrollWheelZoom={false} style={{height: 400, width: "100%"}}>
      <TileLayer
        attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[51.505, -0.09]}>
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  )
}

export default Map
Run Code Online (Sandbox Code Playgroud)

我调用了该文件map.tsx并将其放在一个名为的文件夹中,componentsmap.jsx如果您不使用 TypeScript,您可能会调用它。

注意:此代码与嵌入页面的位置位于单独的文件中很重要,否则您仍然会收到window undefined错误。

另请注意:不要忘记指定MapContainer组件的样式,以便它不会呈现为高度/宽度为零的像素。在上面的示例中,我style={{height: 400, width: "100%"}}为此添加了属性。

现在要使用该组件,请像这样利用 NextJS 的动态加载:

import dynamic from 'next/dynamic'

function HomePage() {
  const Map = dynamic(
    () => import('@components/map'), // replace '@components/map' with your component's location
    { ssr: false } // This line is important. It's what prevents server-side render
  )
  return <Map />
}

export default HomePage
Run Code Online (Sandbox Code Playgroud)

如果您希望在加载时将地图替换为其他内容(可能是一个好习惯),您应该使用动态函数的加载属性,如下所示:

import dynamic from 'next/dynamic'

function HomePage() {
  const Map = dynamic(
    () => import('@components/map'), // replace '@components/map' with your component's location
    { 
      loading: () => <p>A map is loading</p>,
      ssr: false // This line is important. It's what prevents server-side render
    }
  )
  return <Map />
}

export default HomePage
Run Code Online (Sandbox Code Playgroud)

Adrian Ciura 评论了在您的组件重新渲染时可能发生的闪烁,即使地图没有任何变化。他们建议使用新的React.useMemo钩子来解决这个问题。如果这样做,您的代码可能如下所示:

import React from 'react'
import dynamic from 'next/dynamic'

function HomePage() {
  const Map = React.useMemo(() => dynamic(
    () => import('@components/map'), // replace '@components/map' with your component's location
    { 
      loading: () => <p>A map is loading</p>,
      ssr: false // This line is important. It's what prevents server-side render
    }
  ), [/* list variables which should trigger a re-render here */])
  return <Map />
}

export default HomePage
Run Code Online (Sandbox Code Playgroud)

我希望这有帮助。如果react-leaflet对 的存在进行测试会更容易,window因此它可以正常失败,但此解决方法应该在此之前有效。

  • 感谢这个很好的答案。适用于我最新的 React17 和 NextJs 10。一个补充是将地图包含在 Memo 中,否则它会在每次渲染时闪烁。即: ```const Map = React.useMemo(() =&gt;dynamic(() =&gt; import('../components/Map'), { loading: () =&gt; &lt;p&gt;正在加载地图...&lt; /p&gt;, ssr: false, }), []) ``` (4认同)
  • @AdrianCiura 你刚刚让我免于将笔记本电脑扔出窗外。谢谢你! (2认同)

小智 9

从传单或反应传单导入的任何组件都应使用选项 ssr false 动态导入。

import dynamic from 'next/dynamic';

const MyAwesomeMap = dynamic(() => import('components/MyAwesomeMap'), { ssr: false });
Run Code Online (Sandbox Code Playgroud)

Leaflet 认为这超出了他们的范围,因为他们只对普通 JS 环境中发生的问题提供支持,所以他们不会修复(到目前为止)

  • 该解决方案对我有用,但也有一个注意事项:您应该考虑不要将地图组件放入“pages”目录中。我犯了这样的错误,并且挣扎了几天,直到将它们从“页面”文件夹中移出。 (2认同)

fel*_*osh 7

window 在 SSR 中不可用,您可能会在 SSR 环境中收到此错误。

解决这个问题的一种方法是在浏览器中标记组件何时加载(通过使用componentDidMount方法),然后才呈现您window需要的组件。

class MyComp extends React.Component {
  state = {
    inBrowser: false,
  };

  componentDidMount() {
    this.setState({ inBrowser: true });
  }

  render() {
    if (!this.state.inBrowser) {
      return null;
    }

    return <YourRegularComponent />;
  }
}
Run Code Online (Sandbox Code Playgroud)

这将起作用,因为componentDidMount生命周期方法浏览器中被调用。

编辑 - 添加“钩子”方式

import { useEffect, useState } from 'react';

const MyComp = () => {
  const [isBrowser, setIsBrowser] = useState(false);
  useEffect(() => {
    setIsBrowser(true);
  }, []);

  if (!isBrowser) {
    return null;
  }

  return <YourRegularComponent />;
};
Run Code Online (Sandbox Code Playgroud)

useEffecthook 是一种componentDidMount仅在浏览器内运行的替代方法。