繁体   English   中英

如何在没有外部库的情况下在 React 中实现 Google Maps JS API?

[英]How to implement Google Maps JS API in React without an external library?

我正在尝试在 React 中实现外部 API,并希望能够使用 Google Maps 的 API 在子组件中显示地图。 理想情况下,我想了解如何在没有任何外部库的情况下做到这一点,以便在使用 Axios 之类的东西之前对该过程有一个基本的了解。

我的问题是:如何在 React 中使用 Google 文档中的以下代码片段来处理 API?

<script async defer
      src='https://maps.googleapis.com/maps/api/js?key=AIzaSyDZfVO29Iytspv4xz7S68doIoiztiRLhbk&callback=initMap'>
</script>

我尝试在 index.html 文件中使用它,但是当我在 React 的子组件中引用 google 对象时,出现错误:

./src/Main.js 第 114 行:'google' 未定义 no-undef

即使这不是首选或最优雅的方法,对如何在没有任何外部库的情况下实现 API 的一些基本了解也将不胜感激。 谢谢!

编辑:

我的 App.js:

import React, { Component } from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import Main from './Main';

import logo from './logo.svg';
import './App.css';
import injectTapEventPlugin from 'react-tap-event-plugin';

injectTapEventPlugin();

class App extends Component {
  render() {
    return (
      <MuiThemeProvider>
        <Main />
      </MuiThemeProvider>
    );
  }
}

export default App;

我的 Main.js:

import React, { Component } from 'react';
import { FlatButton, Dialog, Card, Drawer, Paper, AppBar, Popover, Menu, MenuItem } from 'material-ui';

var items = [
    {
        id: 0,
        name: 'Test 1',
        city: 'Toronto',
        longitude: 24.42142422,
        latitude: 49.24121415,
        tags: ['vegan', 'cheap', 'low-calorie'],
        reviews: [
            {
                rating: 5,
                reviewText: 'This was an amazing restaurant. Incredibly fast service, a large variety of options, and delicious food. I\'ll be here often',
                author: 'Mohammad Sheikh',
                date: new Date(),
                helpfulCount: 5,
                notHelpfulCount: 4
            },
            {
                rating: 2,
                reviewText: 'Absolutely horrible. Please stop making food.',
                author: 'Dissatisfied Customer',
                date: new Date(),
                helpCount: 2,
                notHelpfulCount: 3
            },
        ],
        foods: 
        [
            {
                id: 0,
                name: 'Salad',
                img: 'http://www.images.google.com/',
                tags: ['vegan', 'low-calorie', 'cheap'],
                nutrition: 
                {
                    calories: 300,
                    fat: 5,
                    carbs: 40,
                    protein: 24
                },
                reviews: 
                {
                    rating: 4,
                    reviewText: 'Decent salad. Would recommend.',
                    author: 'Vegan Bro',
                    date: new Date(),
                    helpCount: 4,
                    notHelpfulCount: 1
                }  
            },
            {
                id: 1,
                name: 'Pasta',
                img: 'http://www.images.google.com/',
                tags: ['vegetarian', 'dinner'],
                nutrition: 
                {
                    calories: 800,
                    fat: 40,
                    carbs: 80,
                    protein: 20
                },
                reviews: 
                {
                    rating: 5,
                    reviewText: 'Absolutely amazing',
                    author: 'Food Fan',
                    date: new Date(),
                    helpCount: 8,
                    notHelpfulCount: 4
                }  
            },
        ],
    },
];

const paperStyle = {
  height: 100,
  width: 100,
  margin: 20,
  textAlign: 'center',
  display: 'table',
  position: 'relative',
  clear: 'both',
  float: 'right',
  zIndex: 6
};

const paperContent = {
    position: 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)'
}

class RestaurantDialog extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            open: false
        }
    }

    render() {
        return (
            <Dialog>
            </Dialog>
        )
    }
}

class RestaurantButton extends React.Component {
    constructor(props) {
        super(props);
    }

    handleClick = () => {

    }

    render() {
        return (
            <FlatButton style={{width: '100%', height: '64px'}} onClick>
                {this.props.item.city}
                <RestaurantDialog restaurant={this.props.item.name} />
            </FlatButton>
        )
    }
}

class MapComponent extends React.Component {

    constructor(props) {
        super(props);
        this.googleChecker = this.googleChecker.bind(this);
        this.renderMap = this.renderMap.bind(this);
    }

    googleChecker() {
        if (!window.google.maps) {
            setTimeout(this.googleChecker, 100);
        }
        else {
            this.renderMap();
        }
    }

    renderMap() {
        var map = google.maps.Map(document.getElementById('map'), {
            zoom: 4,
            center: {lat: 0, lng: 0}
        });
    }

    componentDidMount() {
        this.googleChecker();
    }

    render() {

        const selections = this.props.currentSelections;
        const buttons = items.filter((item) => {
            for (let i = 0; i < selections.length; i++) {
                if (selections.map((selection) => {return selection.toLowerCase()}).indexOf(item.tags[i].toLowerCase()) > -1) {
                    return true;
                }
            }}).map((item) => {
                return (
                    <RestaurantButton style={{zIndex: '5'}} item={item} />
                )
            });

        return (
            <Paper id='map' zDepth={3} style={{height: '300px', width: '100%', backgroundColor: 'white', position: 'absolute'}}>
                { buttons }
            </Paper>
        )
    }
}

class SelectionIcon extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <Paper circle={true} zDepth={5} style={this.props.style} key={this.props.index} onClick={this.props.close} >
                <div style={paperContent}>{this.props.item}</div>
            </Paper>
        )
    }
}

class SelectionIcons extends React.Component {
    constructor(props) {
        super(props);

    }

    handleSelectionClose = (e) => {
        e.currentTarget.open = false;
    }

    render() {

    let currentSelections = this.props.currentSelections.slice();
    let list = currentSelections.map((item, i) => {
        return (
            <Paper circle={true} zDepth={5} style={paperStyle} key={i} onClick={this.handleSelectionClose}>
                <div style={paperContent}>{item}</div>
            </Paper>
        )
    });

        return (
            <div>
                {list}
            </div>
        )
    }
}

class Main extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            navMenuOpen: false,
            currentSelections: []
        }

    }

    handleMenuButtonTouch = (e) => {
        this.setState({
            anchorEl: e.currentTarget.parentNode,
            navMenuOpen: !this.state.navMenuOpen
        })
    }

    handleRequestChange = (change) => {
        this.setState({
            navMenuOpen: change.open
        })
         console.log(document.getElementById('test').style);
    }

    handleMenuClick = (e) => {
        let currentSelections = this.state.currentSelections.slice();
        if (currentSelections.indexOf(e) > -1) {
            currentSelections.splice(currentSelections.indexOf(e), 1);
        }
        else {
            currentSelections.push(e);
        }
        console.log(currentSelections)
        this.setState({ currentSelections });
     }

    render() {
        return (
            <div>
                <AppBar title='The App' id='test' zDepth={1} onLeftIconButtonTouchTap={this.handleMenuButtonTouch} style={{zIndex: 4}}> 
                </AppBar>
                <Drawer 
                        id='test2'
                        open={this.state.navMenuOpen}
                        onRequestChange={() => {this.handleRequestChange;}}
                        containerStyle={{zIndex: 3, marginTop: '64px'}}>

                        <Menu>
                            <MenuItem primaryText='High Protein' onClick={() => this.handleMenuClick('High Protein')} />
                            <MenuItem primaryText='Vegetarian' onClick={() => this.handleMenuClick('Vegetarian')} />
                            <MenuItem primaryText='Vegan' onClick={() => this.handleMenuClick('Vegan')} />
                        </Menu>
                    </Drawer> 
                <MapComponent items={items} currentSelections={this.state.currentSelections} />
                <SelectionIcons currentSelections={this.state.currentSelections} />  
            </div>
        )
    }
}


export default Main;

我的 index.html:

<!doctype html>
<html lang="en">
  <head>  
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <!--
      manifest.json provides metadata used when your web app is added to the
      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <script async defer
      src='https://maps.googleapis.com/maps/api/js?key=AIzaSyDZfVO29Iytspv4xz7S68doIoiztiRLhbk'>
    </script>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

这个问题与使用谷歌地图 API 时 async 和 defer 的工作方式有关。

基本上,当您的代码到达必须呈现地图的点时,Google API 尚未加载。 请查看这篇文章以了解其工作原理:

https://stackoverflow.com/a/36909530/2456879

有两种解决方案。

解决方案一不要在脚本标记中使用 async 和 defer 以允许在执行应用程序之前下载脚本:

<script src='https://maps.googleapis.com/maps/api/js?key=AIzaSyDZfVO29Iytspv4xz7S68doIoiztiRLhbk&callback=initMap'>
</script>

解决方案二创建某种递归检查器,以查看 google api 是否已加载,以便在 google maps api 可用后继续执行您的应用程序:

class MyMap extends Component{

  constructor(props){
    super(props);
    this.googleChecker = this.googleChecker.bind(this);
    this.renderMap = this.renderMap.bind(this);
  }

  googleChecker() {
    // check for maps in case you're using other google api
    if(!window.google.maps) {
      setTimeout(googleChecker, 100);
      console.log("not there yet");
    } else {
      console.log("we're good to go!!");
      // the google maps api is ready to use, render the map
      this.renderMap();
    }
  }

  renderMap(){
    const coords = { lat: 41.375885, lng: 2.177813 };
    // create map instance
    new google.maps.Map(this.refs.mapContainer, {
      zoom: 16,
      center: {
        lat: coords.lat,
        lng: coords.lng
      }
    });
  }

  componentDidMount(){
    this.googleChecker();
  }

  render(){
    return(
      <div className="card map-holder">
        <div className="card-block" ref="mapContainer" />
      </div>
    );
  }
}

您还可以使用 promise 并在 checker 方法或类似方法中解决它。 您也可以将该代码放在父组件中,在状态中存储一个布尔值并将其传递给子组件,以便在 api 可用后开始渲染地图。 这种方法也可以与 redux 和 redux thunk 一起使用以解决承诺。 如您所见,根据您的方法,有一些替代方案。

这是使用超时检查器的实时示例:

https://jsbin.com/tejutihoka/edit?js,output

...仅使用 Hooks。 此解决方案使用库,但它是 Google 自己的加载程序: https : //developers.google.com/maps/documentation/javascript/overview#js_api_loader_package

// https://developers.google.com/maps/documentation/javascript/overview#js_api_loader_package

import { useState, useEffect, useRef } from "react";
import { Loader } from "@googlemaps/js-api-loader";

export default function Map({
  apiKey = "",
  label = "",
  zoom = 16,
  coords = {
    lat: 0,
    lng: 0,
  },
}) {
  const [gmapWin, setGmapWin] = useState(false);
  const [gmapObj, setGmapObj] = useState();
  const mapBox = useRef();
  const props = useRef({ apiKey, label, coords, zoom });

  // load map
  useEffect(() => {
    const loader = new Loader({
      apiKey: props.current.apiKey,
      version: "weekly",
    });

    // https://stackoverflow.com/a/61980156
    const abortController = new AbortController();

    (async function () {
      loader.load().then(() => {
        if (
          !abortController.signal.aborted &&
          window.google?.maps &&
          !gmapWin
        ) {
          setGmapWin(true);
        }
        if (gmapWin) {
          setGmapObj(
            new window.google.maps.Map(mapBox.current, {
              center: props.current.coords,
              zoom: props.current.zoom,
            })
          );
        }
      });
    })();

    return () => {
      abortController.abort();
    };

  }, [gmapWin]);


  // add marker
  useEffect(() => {
    if (gmapObj) {
      new window.google.maps.Marker({
        position: props.current.coords,
        map: gmapObj,
        label: props.current.label,
      });
    }
  }, [gmapObj]);


  return <div className="map" ref={mapBox} />;

};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM