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

Posted

技术标签:

【中文标题】如何在没有外部库的情况下在 React 中实现 Google Maps JS API?【英文标题】:How to implement Google Maps JS API in React without an external library? 【发布时间】: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>

【问题讨论】:

【参考方案1】:

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

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

https://***.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://***.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 />;

;

【讨论】:

以上是关于如何在没有外部库的情况下在 React 中实现 Google Maps JS API?的主要内容,如果未能解决你的问题,请参考以下文章

我可以在没有自己的后端服务器的情况下在 React 中实现 Stripe 结账吗?

如何在没有 Flash 的情况下在 Firefox 中实现复制到剪贴板。需要实施[重复]

如何在没有选择按钮的情况下在 GridView 中实现全行选择?

如何在没有中间副本的情况下在标准 C 中实现 memmove?

如何在没有正则表达式的情况下在 C++ 中实现有效的全字字符串替换?

如何在没有像 Quartz Clustering 这样的集中式解决方案的情况下在分布式环境中实现 cron 作业/计划作业? [复制]