הוספת מפה וסמנים לאפליקציה רספונסיבית

סקירה כללית

במדריך הזה תלמדו איך להוסיף מפה וסמני מיקום לאפליקציית React באמצעות @googlemaps/react-wrapper ולשלב את המפה והסמנים במצב האפליקציה.

התקנה של @googlemaps/react-wrapper

צריך להתקין את הספרייה @googlemaps/react-wrapper ולהשתמש בה כדי לטעון באופן דינמי את ה-API של מפות Google ל-JavaScript כאשר הרכיב נמצא בעיבוד.

npm install @googlemaps/react-wrapper

ניתן לייבא את הספרייה הזו ולהשתמש בה עם הפריטים הבאים.

import { Wrapper, Status } from "@googlemaps/react-wrapper";

השימוש הבסיסי ברכיב הזה הוא לשלב בין רכיבי צאצא שתלויים ב-JavaScript JavaScript API. הרכיב Wrapper מקבל גם רכיב render לעיבוד רכיבים בטעינה או לטיפול בשגיאות בטעינת ה-API של מפות JavaScript.

const render = (status: Status) => {
  return <h1>{status}</h1>;
};

<Wrapper apiKey={"YOUR_API_KEY"} render={render}>
  <YourComponent/>
</Wrapper>

הוספת רכיב מפה

סביר להניח שרכיב פונקציונלי בסיסי לעיבוד מפה ישתמש ב-useRef , useState ו-useEffect תגובות webhook.

לרכיב המפה ההתחלתית תהיה החתימה הבאה.

const Map: React.FC<{}> = () => {};

מאחר שהפונקציה google.maps.Map מחייבת Element בתור פרמטר בונה, יש להשתמש ב-useRef כדי לשמור על אובייקט בר שינוי שיימשך כל משך החיים של הרכיב. קטע הקוד הבא יוצר מפה בתוך הוו useImpact בגוף הרכיב Map.

TypeScript

const ref = React.useRef<HTMLDivElement>(null);
const [map, setMap] = React.useState<google.maps.Map>();

React.useEffect(() => {
  if (ref.current && !map) {
    setMap(new window.google.maps.Map(ref.current, {}));
  }
}, [ref, map]);

JavaScript

const ref = React.useRef(null);
const [map, setMap] = React.useState();

React.useEffect(() => {
  if (ref.current && !map) {
    setMap(new window.google.maps.Map(ref.current, {}));
  }
}, [ref, map]);

הווריאנט useEffect שלמעלה יפעל רק כשהref השתנה. הרכיב Map מחזיר עכשיו את הרכיבים הבאים.

return <div ref={ref} />

הרחבת רכיב המפה עם אביזרים נוספים

ניתן להרחיב את רכיב המפה הבסיסי באמצעות אביזרים נוספים עבור אפשרויות מפה, פונקציות מסוג event event וסגנונות שונים של מידע שחלים על ה-div שמכיל את המפה. הקוד הבא מציג את הממשק המורחב של הרכיב הפונקציונלי הזה.

interface MapProps extends google.maps.MapOptions {
  style: { [key: string]: string };
  onClick?: (e: google.maps.MapMouseEvent) => void;
  onIdle?: (map: google.maps.Map) => void;
}

const Map: React.FC<MapProps> = ({
  onClick,
  onIdle,
  children,
  style,
  ...options
}) => {}

האובייקט style יכול לעבור ישירות ולהגדיר אותו כאביזר ב-div המעובד.

return <div ref={ref} style={style} />;

לפי הדרישות של onClick, onIdle ו-google.maps.MapOptions, נדרשים קריטריונים של useEffect כדי להחיל עדכונים על google.maps.Map באופן חיוני.

TypeScript

// because React does not do deep comparisons, a custom hook is used
// see discussion in https://github.com/googlemaps/js-samples/issues/946
useDeepCompareEffectForMaps(() => {
  if (map) {
    map.setOptions(options);
  }
}, [map, options]);

JavaScript

// because React does not do deep comparisons, a custom hook is used
// see discussion in https://github.com/googlemaps/js-samples/issues/946
useDeepCompareEffectForMaps(() => {
  if (map) {
    map.setOptions(options);
  }
}, [map, options]);

פונקציות מסוג listener של אירוע מחייבות קוד מורכב יותר כדי לנקות פונקציות האזנה קיימות כאשר handler עובר באופן ידני כאביזר.

TypeScript

React.useEffect(() => {
  if (map) {
    ["click", "idle"].forEach((eventName) =>
      google.maps.event.clearListeners(map, eventName)
    );

    if (onClick) {
      map.addListener("click", onClick);
    }

    if (onIdle) {
      map.addListener("idle", () => onIdle(map));
    }
  }
}, [map, onClick, onIdle]);

JavaScript

React.useEffect(() => {
  if (map) {
    ["click", "idle"].forEach((eventName) =>
      google.maps.event.clearListeners(map, eventName)
    );
    if (onClick) {
      map.addListener("click", onClick);
    }

    if (onIdle) {
      map.addListener("idle", () => onIdle(map));
    }
  }
}, [map, onClick, onIdle]);

בניית רכיב סמן

ברכיב הסמן נעשה שימוש בדפוסים דומים לרכיב המפה עם ווים useEffect ו-useState.

TypeScript

const Marker: React.FC<google.maps.MarkerOptions> = (options) => {
  const [marker, setMarker] = React.useState<google.maps.Marker>();

  React.useEffect(() => {
    if (!marker) {
      setMarker(new google.maps.Marker());
    }

    // remove marker from map on unmount
    return () => {
      if (marker) {
        marker.setMap(null);
      }
    };
  }, [marker]);

  React.useEffect(() => {
    if (marker) {
      marker.setOptions(options);
    }
  }, [marker, options]);

  return null;
};

JavaScript

const Marker = (options) => {
  const [marker, setMarker] = React.useState();

  React.useEffect(() => {
    if (!marker) {
      setMarker(new google.maps.Marker());
    }

    // remove marker from map on unmount
    return () => {
      if (marker) {
        marker.setMap(null);
      }
    };
  }, [marker]);
  React.useEffect(() => {
    if (marker) {
      marker.setOptions(options);
    }
  }, [marker, options]);
  return null;
};

הרכיב מחזיר ערך ריק כאשר google.maps.Map מנהל את השינויים ב-DOM.

הוספת סמנים כרכיב צאצא של המפה

כדי להוסיף את הסמנים למפה, הרכיב Marker יועבר לרכיב Map באמצעות האביזר המיוחד children כפי שמתואר בהמשך.

<Wrapper apiKey={"YOUR_API_KEY"}>
  <Map center={center} zoom={zoom}>
    <Marker position={position} />
  </Map>
</Wrapper>

צריך לבצע שינוי קטן בפלט של הרכיב Map כדי להעביר את האובייקט google.maps.Map לכל הילדים כאביזר נוסף.

TypeScript

return (
  <>
    <div ref={ref} style={style} />
    {React.Children.map(children, (child) => {
      if (React.isValidElement(child)) {
        // set the map prop on the child component
        // @ts-ignore
        return React.cloneElement(child, { map });
      }
    })}
  </>
);

JavaScript

return (
  <>
    <div ref={ref} style={style} />
    {React.Children.map(children, (child) => {
      if (React.isValidElement(child)) {
        // set the map prop on the child component
        // @ts-ignore
        return React.cloneElement(child, { map });
      }
    })}
  </>
);

מצב מפה ומצב האפליקציה

באמצעות התבנית שלמעלה עבור התקשרות חזרה ב-onClick וב-onIdle, ניתן להרחיב את האפליקציה כך שהיא תשלב באופן מלא פעולות משתמש כמו לחיצה או הזזת המפה.

TypeScript

const [clicks, setClicks] = React.useState<google.maps.LatLng[]>([]);
const [zoom, setZoom] = React.useState(3); // initial zoom
const [center, setCenter] = React.useState<google.maps.LatLngLiteral>({
  lat: 0,
  lng: 0,
});

const onClick = (e: google.maps.MapMouseEvent) => {
  // avoid directly mutating state
  setClicks([...clicks, e.latLng!]);
};

const onIdle = (m: google.maps.Map) => {
  console.log("onIdle");
  setZoom(m.getZoom()!);
  setCenter(m.getCenter()!.toJSON());
};

JavaScript

const [clicks, setClicks] = React.useState([]);
const [zoom, setZoom] = React.useState(3); // initial zoom
const [center, setCenter] = React.useState({
  lat: 0,
  lng: 0,
});

const onClick = (e) => {
  // avoid directly mutating state
  setClicks([...clicks, e.latLng]);
};

const onIdle = (m) => {
  console.log("onIdle");
  setZoom(m.getZoom());
  setCenter(m.getCenter().toJSON());
};

את השקים האלה אפשר לשלב ברכיבי הטופס, תוך שימוש בתבנית הבאה כפי שמתואר בקלט של קו הרוחב.

<label htmlFor="lat">Latitude</label>
<input
  type="number"
  id="lat"
  name="lat"
  value={center.lat}
  onChange={(event) =>
    setCenter({ ...center, lat: Number(event.target.value) })
  }
/>

לבסוף, האפליקציה עוקבת אחר קליקים והיא מפעילה סמנים בכל מיקום שבו מתבצע הקליק. הקוד הבא משתמש במשתנה position כדי להעביר את הערך LatLng מאירוע הקליק.

{clicks.map((latLng, i) => (<Marker key={i} position={latLng} />))}

קוד Explore

אפשר לחקור את הקוד המלא באמצעות גני המשחקים של הקוד שבהמשך או על ידי שכפול מאגר ה-git.

לדגום

שכפול הדגימה

Git ו-Node.js נדרשים כדי להריץ את הדוגמה הזו באופן מקומי. כדי להתקין את Node.js ו-NPM, פועלים לפי ההוראות האלה. הפקודות הבאות משכפלות, מתקינים יחסי תלות ומפעילים את האפליקציה לדוגמה.

  git clone -b sample-react-map https://github.com/googlemaps/js-samples.git
  cd js-samples
  npm i
  npm start

אפשר לנסות דוגמאות נוספות על ידי מעבר לכל סניף שמתחיל ב-sample-SAMPLE_NAME.

  git checkout sample-SAMPLE_NAME
  npm i
  npm start