React:导出函数以在另一个组件中使用

React: Export function to use in another component

我正在我的 Gatsby 网站上构建一个 Google 地图,其中包含一个搜索框,允许用户搜索他们的位置。我有一个 Hero 组件和一个 Map 组件,所有功能都内置在 Map 组件中; Google 地图 API、自动完成、Google 地点等。这是现在的样子:

Map.tsx

import React, { useState, useRef, useCallback } from 'react';
import { GoogleMap, useLoadScript, Marker, InfoWindow } from '@react-google-maps/api';
import * as dealersData from 'assets/data/dealers.json';

import { Button } from 'components/button/Button';

import MapMarker from 'assets/images/icons/map-marker.png';
import SearchIcon from 'assets/svg/search.svg';

import usePlacesAutocomplete, { getGeocode, getLatLng } from 'use-places-autocomplete';

import {
  Combobox,
  ComboboxInput,
  ComboboxPopover,
  ComboboxList,
  ComboboxOption,
} from '@reach/combobox';
import '@reach/combobox/styles.css';

import s from './Map.scss';
import MapStyles from './_MapStyles';

const libraries = ['places'];

const mapContainerStyle = {
  width: '100%',
  height: '100%',
};

const options = {
  styles: MapStyles,
  disableDefaultUI: true,
  zoomControl: true,
};

interface MapProps {
  location: any;
  fetchedData: ReactNode;
}

export const Map = ({ location, fetchedData }: MapProps) => {

  const center = {
    lat: location.location.latitude,
    lng: location.location.longitude,
  };

  const [selectedDealer, setselectedDealer] = useState(null);

  const { isLoaded, loadError } = useLoadScript({
    googleMapsApiKey: process.env.GATSBY_GOOGLE_MAPS_API,
    libraries,
  });

  const mapRef = useRef();
  const onMapLoad = useCallback((map) => {
    mapRef.current = map;
  }, []);

  const panTo = useCallback(({ lat, lng }) => {
    mapRef.current.panTo({ lat, lng });
    mapRef.current.setZoom(10);
  }, []);

  if (loadError) return <div className={s.map}>Error loading maps...</div>;
  if (!isLoaded) return <div className={s.map}>Loading...</div>;

  return (
    <>
      <div className={s.map}>
        <div className={s.map__search}>
          <Search panTo={panTo}></Search>
        </div>

        <GoogleMap
          mapContainerStyle={mapContainerStyle}
          zoom={10}
          center={center}
          options={options}
          onLoad={onMapLoad}
        >
          {dealersData.features.map((dealer) => (
            <Marker
              key={dealer.properties.DEALER_ID}
              position={{
                lat: dealer.geometry.coordinates[1],
                lng: dealer.geometry.coordinates[0],
              }}
              onClick={() => {
                setselectedDealer(dealer);
              }}
              icon={{
                url: MapMarker,
                scaledSize: new window.google.maps.Size(30, 30),
              }}
            />
          ))}

          {selectedDealer && (
            <InfoWindow
              position={{
                lat: selectedDealer.geometry.coordinates[1],
                lng: selectedDealer.geometry.coordinates[0],
              }}
              onCloseClick={() => {
                setselectedDealer(null);
              }}
            >
              <div className={s.dealer}>
                <h2 className={s.dealer__name}>
                  {selectedDealer.properties.NAME}
                  <span className={s.phone}>{selectedDealer.properties.PHONE_NUMBER}</span>
                </h2>
                <div className={s.dealer__info}>
                  <p className={s.address}>{selectedDealer.properties.ADDRESS}</p>
                  <p className={s.address}>
                    {selectedDealer.properties.CITY}, {selectedDealer.properties.STATE}{' '}
                    {selectedDealer.properties.ZIP_CODE}
                  </p>
                </div>
                <Button>Set Location</Button>
              </div>
            </InfoWindow>
          )}
        </GoogleMap>
      </div>
    </>
  );
};

export function Search({ panTo }) {
  const {
    ready,
    value,
    suggestions: { status, data },
    setValue,
    clearSuggestions,
  } = usePlacesAutocomplete({
    requestOptions: {
      location: { lat: () => 38.8299359, lng: () => -121.3070356 },
      radius: 200 * 1000,
    },
  });

  return (
    <>
      <div className={s.search__input}>
        <i className={s.search__icon}>
          <SearchIcon />
        </i>
        <Combobox
          onSelect={async (address) => {
            setValue(address, false);
            clearSuggestions();

            try {
              const results = await getGeocode({ address });
              const { lat, lng } = await getLatLng(results[0]);
              panTo({ lat, lng });
            } catch (error) {
              console.log('error');
            }
          }}
        >
          <ComboboxInput
            value={value}
            onChange={(e: any) => {
              setValue(e.target.value);
            }}
            disabled={!ready}
            placeholder="Enter your location"
          />
          <ComboboxPopover>
            <ComboboxList>
              {status === 'OK' &&
                data.map(({ id, description }) => <ComboboxOption key={id} value={description} />)}
            </ComboboxList>
          </ComboboxPopover>
        </Combobox>
      </div>
    </>
  );
}

这是我的英雄组件:

Hero.tsx

import React from 'react';

import s from './Hero.scss';

import { RichText } from 'prismic-reactjs';

import { linkResolver } from 'utils/linkResolver';
import htmlSerializer from 'utils/htmlSerializer';

import { Search } from '../map/_Map';

export const Hero = ({ content, panTo }: any) => (
  <div className={s.hero} data-theme={content.theme}>
    <div className={s.hero__container}>
      <div className={s.content}>
        <h1 className={s.content__heading}>{RichText.asText(content.page_title)}</h1>
        <div className={s.content__copy}>
          {RichText.render(content.copy, linkResolver, htmlSerializer)}
        </div>
        <Search panTo={panTo}></Search>
      </div>
    </div>
  </div>
);

本质上,我需要在 Hero 组件中使用 Search 函数,但是当我导出它并将其导入 Hero 时,它的渲染效果很好,但是它从不加载 use-places-autocomplete 库,并且不会工作。我究竟做错了什么?有什么办法导出搜索功能可以重复使用吗?

我已经按照此处评论中的要求创建了一个 SSCCE。如果我直接在 Map 组件中使用它,搜索功能可以使用,但是如果将它导入到 hero 中,它不会加载。

https://codesandbox.io/s/dawn-glitter-zi7lx

谢谢。

感谢您在 codesandbox 中提供代码的 sscce。您似乎只在 <Map/> 内而不是 <Hero/> 内加载 Google 地图 Javascript 脚本标签。您可以在您的控制台日志中看到一条加载库的消息。

为了让它发挥作用,我在你的 index.tsx 中使用了 @react-google-maps/api 的 LoadScript 并将两者和组件按顺序排列脚本工作。这是 index.tsx.

的示例
import React from "react";
import ReactDOM from "react-dom";
import { LoadScript } from "@react-google-maps/api";

import { Map } from "./Map";
import { Hero } from "./Hero";

const rootElement = document.getElementById("root");

const App = () => {
  return (
    <React.StrictMode>
      <LoadScript
        googleMapsApiKey="YOUR_API_KEY"
        libraries={["places"]}
      >
        <Hero />
        <Map />
      </LoadScript>
    </React.StrictMode>
  );
};

ReactDOM.render(<App />, rootElement);

我还删除了 Map.tsx 中的 useLoadScript 以确保它不会与 index.tsx 中加载的脚本发生冲突。