2
我在使用this教程編寫一個Google Maps React組件,該組件會延遲加載庫。我從this的要點實現了ScriptCache。我遇到的問題是,代碼只顯示Loading map...
,並且地圖從不呈現。我錯過了什麼明顯的事情?使用TypeScript延遲加載谷歌地圖庫
的index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
<title>App</title>
</head>
<body>
<div id="app"></div>
<script src="/Scripts/dist/bundle.js"></script>
</body>
</html>
index.tsx:
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as ReactRouter from "react-router";
import * as ReactBootstrap from "react-bootstrap";
import Container from "./Google/GoogleMapComponent";
ReactDOM.render(
<div>
<Container google={(window as any).google} />
</div>,
document.getElementById("app")
);
GoogleApi.tsx:
export const GoogleApi = function (opts: any) {
opts = opts || {}
const apiKey: any = opts.apiKey;
const libraries: any = opts.libraries || [];
const client: any = opts.client;
const URL: string = 'https://maps.googleapis.com/maps/api/js';
const googleVersion: string = '3.22';
let script: any = null;
let google: any = (window as any).google = null;
let loading = false;
let channel: any = null;
let language: any = null;
let region: any = null;
let onLoadEvents: any[] = [];
const url =() => {
let url = URL;
let params = {
key: apiKey,
callback: 'CALLBACK_NAME',
libraries: libraries.join(','),
client: client,
v: googleVersion,
channel: channel,
language: language,
region: region
}
let paramStr = Object.keys(params)
.filter(k => !!(params as any)[k])
.map(k => `${k}=${(params as any)[k]}`).join('&');
return `${url}?${paramStr}`;
}
return url();
}
export default GoogleApi
GoogleApiComponent.tsx:
import * as React from "react";
import * as ReactDOM from 'react-dom'
import cache from './ScriptCache'
import GoogleApi from './GoogleApi'
const defaultMapConfig = {}
export const wrapper = (options: any) => (WrappedComponent: any) => {
const apiKey = options.apiKey;
const libraries = options.libraries || ['places'];
class Wrapper extends React.Component<any, any> {
constructor(props: any, context: any) {
super(props, context);
this.state = {
loaded: false,
map: null,
google: null
}
}
scriptCache: any;
map: any;
mapComponent: any
refs: {
[string: string]: any;
map: any;
}
componentDidMount() {
const refs: any = this.refs;
this.scriptCache.google.onLoad((err: any, tag: any) => {
const maps = (window as any).google.maps;
const props = Object.assign({}, this.props, {
loaded: this.state.loaded
});
const mapRef: any = refs.map;
const node = ReactDOM.findDOMNode(mapRef);
let center = new maps.LatLng(this.props.lat, this.props.lng)
let mapConfig = Object.assign({}, defaultMapConfig, {
center, zoom: this.props.zoom
})
this.map = new maps.Map(node, mapConfig);
this.setState({
loaded: true,
map: this.map,
google: (window as any).google
})
});
}
componentWillMount() {
this.scriptCache = cache({
google: GoogleApi({
apiKey: apiKey,
libraries: libraries
})
});
}
render() {
const props = Object.assign({}, this.props, {
loaded: this.state.loaded,
map: this.state.map,
google: this.state.google,
mapComponent: this.refs.map
})
return (
<div>
<WrappedComponent {...props} />
<div ref='map' />
</div>
)
}
}
return Wrapper;
}
export default wrapper;
GoogleMapComponent.tsx:
import * as React from "react";
import * as ReactDOM from 'react-dom'
import GoogleApiComponent from "./GoogleApiComponent";
export class Container extends React.Component<any, any> {
render() {
const style = {
width: '100px',
height: '100px'
}
return (
<div style={style}>
<Map google={this.props.google} />
</div>
)
}
}
export default GoogleApiComponent({
apiKey: 'AIzaSyAyesbQMyKVVbBgKVi2g6VX7mop2z96jBo ' //From Fullstackreact.com
})(Container)
export class Map extends React.Component<any, any> {
refs: {
[string: string]: any;
map: any;
}
map: any;
componentDidMount() {
this.loadMap();
}
componentDidUpdate(prevProps: any, prevState: any) {
if (prevProps.google !== this.props.google) {
this.loadMap();
}
}
loadMap() {
if (this.props && this.props.google) {
// google is available
const {google} = this.props;
const maps = google.maps;
const mapRef = this.refs.map;
const node = ReactDOM.findDOMNode(mapRef);
let zoom = 14;
let lat = 37.774929;
let lng = -122.419416;
const center = new maps.LatLng(lat, lng);
const mapConfig = Object.assign({}, {
center: center,
zoom: zoom
})
this.map = new maps.Map(node, mapConfig);
}
// ...
}
render() {
return (
<div ref='map'>
Loading map...
</div>
)
}
}
ScriptCache.tsx:
let counter = 0;
let scriptMap = new Map();
export const ScriptCache = (function (global: any) {
return function ScriptCache(scripts: any) {
const Cache: any = {}
Cache._onLoad = function (key: any) {
return (cb: any) => {
let stored = scriptMap.get(key);
if (stored) {
stored.promise.then(() => {
stored.error ? cb(stored.error) : cb(null, stored)
})
} else {
// TODO:
}
}
}
Cache._scriptTag = (key: any, src: any) => {
if (!scriptMap.has(key)) {
let tag : any = document.createElement('script');
let promise = new Promise((resolve: any, reject: any) => {
let resolved = false,
errored = false,
body = document.getElementsByTagName('body')[0];
tag.type = 'text/javascript';
tag.async = false; // Load in order
const cbName = `loaderCB${counter++}${Date.now()}`;
let cb: any;
let handleResult = (state: any) => {
return (evt: any) => {
let stored = scriptMap.get(key);
if (state === 'loaded') {
stored.resolved = true;
resolve(src);
// stored.handlers.forEach(h => h.call(null, stored))
// stored.handlers = []
} else if (state === 'error') {
stored.errored = true;
// stored.handlers.forEach(h => h.call(null, stored))
// stored.handlers = [];
reject(evt)
}
cleanup();
}
}
const cleanup =() => {
if (global[cbName] && typeof global[cbName] === 'function') {
global[cbName] = null;
}
}
tag.onload = handleResult('loaded');
tag.onerror = handleResult('error')
tag.onreadystatechange =() => {
handleResult(tag.readyState)
}
// Pick off callback, if there is one
if (src.match(/callback=CALLBACK_NAME/)) {
src = src.replace(/(callback=)[^\&]+/, `$1${cbName}`)
cb = (window as any)[cbName] = tag.onload;
} else {
tag.addEventListener('load', tag.onload)
}
tag.addEventListener('error', tag.onerror);
tag.src = src;
body.appendChild(tag);
return tag;
});
let initialState = {
loaded: false,
error: false,
promise: promise,
tag
}
scriptMap.set(key, initialState);
}
return scriptMap.get(key);
}
Object.keys(scripts).forEach(function (key) {
const script = scripts[key];
Cache[key] = {
tag: Cache._scriptTag(key, script),
onLoad: Cache._onLoad(key)
}
})
return Cache;
}
})(window)
export default ScriptCache;
看看我的回答這個帖子:http://stackoverflow.com/a/41710341/1550476 –
@NguyenThanh謝謝,但它使用的experimentalDecorators,我也得到了'警告:通過主陣營包訪問PropTypes已棄用。運行時,在瀏覽器中使用npm中的prop-types包。用當前代碼運行它。 – Ogglas