在 Gatsbyjs (Reactjs) 项目中加载 Google 放置 API

Load Google Place API in Gatsbyjs (Reactjs) project

我正在尝试使用 Google 地点 API 的自动完成地址服务。

找到这个图书馆: https://github.com/kenny-hibino/react-places-autocomplete#load-google-library

它要求在我的项目中加载库: https://github.com/kenny-hibino/react-places-autocomplete#getting-started

如果是纯 Reactjs 项目,我会在 public/index.html 中进行。但是Gatsbyjs项目中的public/index.html会在每次运行:

时被删除并重新生成
Gatsby develop

命令行。

如何在我的 Gatsbyjs 项目中使用 Google Place API?

更新

我已经尝试了两种方法来实现这个。

  1. 在 /layouts/index.js 中使用 React-Helmet,如下所示:

        <Helmet>
          <script src="https://maps.googleapis.com/maps/api/js?key={API}&libraries=places&callback=initAutocomplete" async defer></script>
        </Helmet>
    

  2. 将脚本引用放在 /public/index.html,如下所示:

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charSet="utf-8" />
        <meta http-equiv="x-ua-compatible" content="ie=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <title data-react-helmet="true"></title>
        <script src="/socket.io/socket.io.js"></script>
        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key={API_KEY}&libraries=places" async defer ></script>
    </head>
    
    <body>
        <div id="___gatsby"></div>
        <script src="/commons.js"></script>
    </body>
    
    </html>
    

对于第一个解决方案,每次刷新页面后,项目都会抛出一个错误,要求加载 Google JavaScript 地图 API.

对于第二种解决方案,每次通过命令行重新启动Gatsby后:gatsby develop

它会重新生成 index.html 并清除其中的 JavaScript 引用。

您不应该使用 GatsbyJS.

修改 public 目录中的任何文件

相反,我建议您 customize your html.js file

为此,首先 运行:

cp .cache/default-html.js src/html.js

你应该在 /src/html.js.

中有 html.js 文件

现在您可以将 <script> 标签放在 <head> 中。

2020 年 2 月 24 日更新

这是一个更现代的实现,它使用基于 React.memo 和自定义 shouldUpdate 函数的一些性能优化的 React 钩子。有关详细信息,请参阅 this blog post

import { functions, isEqual, omit } from 'lodash'
import React, { useState, useEffect, useRef } from 'react'

function Map({ options, onMount, className, onMountProps }) {
  const ref = useRef()
  const [map, setMap] = useState()

  useEffect(() => {
    // The Map constructor modifies its options object in place by adding
    // a mapTypeId with default value 'roadmap'. This confuses shouldNotUpdate.
    // { ...options } prevents this by passing in a copy.
    const onLoad = () =>
      setMap(new window.google.maps.Map(ref.current, { ...options }))
    if (!window.google) {
      const script = document.createElement(`script`)
      script.src = `https://maps.googleapis.com/maps/api/js?key=` + YOUR_API_KEY
      document.head.append(script)
      script.addEventListener(`load`, onLoad)
      return () => script.removeEventListener(`load`, onLoad)
    } else onLoad()
  }, [options])

  if (map && typeof onMount === `function`) onMount(map, onMountProps)

  return (
    <div
      style={{ height: `60vh`, margin: ` 1em 0`, borderRadius: ` 0.5em` }}
      {...{ ref, className }}
    />
  )
}

function shouldNotUpdate(props, nextProps) {
  const [funcs, nextFuncs] = [functions(props), functions(nextProps)]
  const noPropChange = isEqual(omit(props, funcs), omit(nextProps, nextFuncs))
  const noFuncChange =
    funcs.length === nextFuncs.length &&
    funcs.every(fn => props[fn].toString() === nextProps[fn].toString())
  return noPropChange && noFuncChange
}

export default React.memo(Map, shouldNotUpdate)

Map.defaultProps = {
  options: {
    center: { lat: 48, lng: 8 },
    zoom: 5,
  },
}

旧答案

使用html.js

像这样修改 src/html.js(如 Nenu 所建议的那样)是一种选择。

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class HTML extends Component {
  render() {
    return (
      <html {...this.props.htmlAttributes}>
        <head>
          <meta charSet="utf-8" />
          <meta httpEquiv="x-ua-compatible" content="ie=edge" />
          <meta
            name="viewport"
            content="width=device-width, initial-scale=1, shrink-to-fit=no"
          />
          {this.props.headComponents}
        </head>
        <body {...this.props.bodyAttributes}>
          {this.props.preBodyComponents}
          <div
            key={`body`}
            id="___gatsby"
            dangerouslySetInnerHTML={{ __html: this.props.body }}
          />
          {this.props.postBodyComponents}
          // MODIFICATION // ===================
          <script
            src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"
            async
            defer
          />
          // ===================
        </body>
      </html>
    )
  }
}

HTML.propTypes = {
  htmlAttributes: PropTypes.object,
  headComponents: PropTypes.array,
  bodyAttributes: PropTypes.object,
  preBodyComponents: PropTypes.array,
  body: PropTypes.string,
  postBodyComponents: PropTypes.array,
}

然后您可以从 window.google.maps.(Map|Marker|etc.).

在项目的任何地方访问 Google 地图 API

React 方式

不过,对我来说,这感觉有点不合时宜。如果你想要一个可重用的 React 组件,你可以将其作为 import Map from './Map' 导入到任何页面或模板中,我建议这样做。 (提示:请参阅下面的更新以获取等效功能组件。)

// src/components/Map.js
import React, { Component } from 'react'

export default class Map extends Component {
  onLoad = () => {
    const map = new window.google.maps.Map(
      document.getElementById(this.props.id),
      this.props.options
    )
    this.props.onMount(map)
  }

  componentDidMount() {
    if (!window.google) {
      const script = document.createElement('script')
      script.type = 'text/javascript'
      script.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY`
      const headScript = document.getElementsByTagName('script')[0]
      headScript.parentNode.insertBefore(script, headScript)
      script.addEventListener('load', () => {
        this.onLoad()
      })
    } else {
      this.onLoad()
    }
  }

  render() {
    return <div style={{ height: `50vh` }} id={this.props.id} />
  }
}

像这样使用它:

// src/pages/contact.js
import React from 'react'

import Map from '../components/Map'

const center = { lat: 50, lng: 10 }
const mapProps = {
  options: {
    center,
    zoom: 8,
  },
  onMount: map => {
    new window.google.maps.Marker({
      position: center,
      map,
      title: 'Europe',
    })
  },
}

export default function Contact() {
  return (
    <>
      <h1>Contact</h1>
      <Map id="contactMap" {...mapProps} />
    </>
  )
}

对我来说醒来的是在我的项目的根目录中创建一个 gatsby-ssr.js 文件,然后在其中包含脚本,如下所示:

import React from "react"

export function onRenderBody({ setHeadComponents }) {
  setHeadComponents([
    <script
      key="abc"
      type="text/javascript"
      src={`https://maps.googleapis.com/maps/api/js?key=${process.env.GATSBY_API_KEY}&libraries=places`}
    />,
  ])
}

不要忘记在 .env.development 和 .env.production 文件中包含 GATSBY_API_KEY 或任何您想调用的名称:

GATSBY_API_KEY=...