2017-04-26 51 views
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; 
+0

看看我的回答這個帖子:http://stackoverflow.com/a/41710341/1550476 –

+0

@NguyenThanh謝謝,但它使用的experimentalDecorators,我也得到了'警告:通過主陣營包訪問PropTypes已棄用。運行時,在瀏覽器中使用npm中的prop-types包。用當前代碼運行它。 – Ogglas

回答

1

即使集裝箱看起來像這樣的事情會得到印:

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 class Map extends React.Component<any, any> { 
... 
    render() { 
     const style = { 
      width: '100vw', 
      height: '100vh' 
     } 
     return (
      <div ref='map' style={style}> 
       Loading map... 
     </div> 
     ) 
    } 
}