【问题标题】:How to implement Google Maps JS API in React without an external library?如何在没有外部库的情况下在 React 中实现 Google Maps JS API?
【发布时间】:2018-01-07 19:52:56
【问题描述】:

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

我的问题是:如何使用 Google 文档中的以下 sn-p 用于 React 中的 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>

【问题讨论】:

    标签: javascript ajax google-maps reactjs google-maps-api-3


    【解决方案1】:

    问题与使用 google maps API 时异步和延迟的工作方式有关。

    基本上,当您的代码到达必须呈现地图的点时,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 一起使用,以解决一个 Promise。如您所见,根据您的方法,有一些替代方案。

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

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

    【讨论】:

    • 感谢您的帮助,但即使在我的组件中实现了 googleChecker 方法后,我仍然收到相同的错误消息,即“'google' is not defined”。这是尝试在 html 中包含和删除 async 和 defer 标签的组合,以及取出 googleChecker、componentDidMount 和 renderMap 方法。
    • @MoSheikh 你能分享你在 jsbin 中的简单实时代码示例吗?只是一个准系统示例,展示了你如何获取谷歌地图并在 React 上使用它。这将有助于更快地解决这个问题。
    • 这是一个没有辅助函数的示例:jsbin.com/ginimayifa/1/edit?html,css,js,output,这是另一个带有您指定的辅助函数的示例:jsbin.com/nikeyovije/edit?html,css,js,output。后者实际上在 jsbin 中工作,但由于某种原因不在我的本地环境中。我已将它完全复制到我的环境中,但由于某种原因它仍然无法正常工作。
    • 我已将我的文件添加到编辑中的主要问题中,我们将不胜感激!
    • 你能用你的代码创建一个实时样本,像这样减少到最低限度吗?:jsbin.com/masowev/edit?html,js我从来没有使用过材质ui,也许这可能会造成问题。您是否尝试过摆脱除主要和地图组件之外的所有内容,只需删除其余部分。通常,跟踪错误的好方法是从最简单的应用程序版本开始,然后开始添加内容,直到问题出现。对于我所看到的代码应该可以正常工作,但还有很多事情要做。最后避免在 React 中使用 document.getElementById(),如果你必须使用 refs。
    【解决方案2】:

    ...仅使用 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} />;
    
    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-04
      相关资源
      最近更新 更多