2017-04-26 3 views
2

Ich bin nach this Tutorial, um eine Google Maps React Component zu schreiben, die die Bibliothek faul lädt. Ich habe ScriptCache von this gist implementiert. Das Problem, das ich habe, ist, dass der Code nur Loading map... zeigt und die Karte nie gerendert wird. Irgendeine offensichtliche Sache, die ich verpasst habe?Lazy laden Google Maps-Bibliothek in Reagieren mit 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

prüfen in diesem Beitrag meine Antwort aus: http://stackoverflow.com/a/41710341/1550476 –

+0

@NguyenThanh Danke, aber es verwendet experimentalDecorators und ich habe auch die 'Achtung: PropTypes über das Haupt Reagieren Paket Zugriff ist veraltet. Verwenden Sie stattdessen das prop-types-Paket von npm in meinem Browser, wenn Sie es ausführen. Habe es mit dem aktuellen Code laufen. – Ogglas

Antwort

1

Obwohl Container wie folgt aussieht nichts gedruckt wird erhalten:

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> 
     ) 
    } 
} 

Wenn ich jedoch die Breite und Höhe eingestellt Auf Karte ref direkt wird es richtig gedruckt. Sie können Breite und Höhe nicht in Prozent hinzufügen.

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