使用TypeScript在React中延迟加载Google Maps库

时间:2017-04-26 08:22:06

标签: google-maps reactjs typescript

我正在按照this教程编写一个延迟加载库的Google Maps React组件。我已经从this gist实现了ScriptCache。我遇到的问题是代码只显示Loading map...,并且从不呈现地图。我错过了哪些显而易见的事情?

的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;

1 个答案:

答案 0 :(得分: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>
        )
    }
}