在 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?
更新
我已经尝试了两种方法来实现这个。
在 /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>
将脚本引用放在 /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=...
我正在尝试使用 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?
更新
我已经尝试了两种方法来实现这个。
在 /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>
将脚本引用放在 /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.)
.
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=...