إضافة خريطة وعلامات إلى تطبيق تفاعل

نظرة عامة

يوضّح لك هذا البرنامج التعليمي كيفية إضافة خريطة ومحدّد موقع إلى تطبيق React باستخدام @googlemaps/react-wrapper ودمج الخريطة والعلامات في حالة التطبيق.

تثبيت @googlemaps/react-wrapper

يمكنك تثبيت مكتبة @googlemaps/react-wrapper واستخدامها لتحميل واجهة برمجة تطبيقات جافا سكريبت للخرائط ديناميكيًا عند عرض المكون.

npm install @googlemaps/react-wrapper

يمكن استيراد هذه المكتبة واستخدامها مع ما يلي.

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

الاستخدام الأساسي لهذا المكوِّن هو لف المكوِّنات الثانوية التي تعتمد على واجهة برمجة تطبيقات JavaScript للخرائط. يقبل المكوّن Wrapper أيضًا دعامة render لعرض مكونات التحميل أو معالجة الأخطاء في تحميل واجهة برمجة تطبيقات JavaScript للخرائط.

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

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

إضافة مكوِّن خريطة

هناك مكوّن وظيفي أساسي لعرض خريطة، ومن المرجح أن يستفيد من الخطّاطات المتفاعلة في useRef وuseState وuseEffect..

سيكون للمكوّن الأساسي في الخريطة التوقيع التالي.

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

نظرًا لأن google.maps.Map تتطلب Element كمعلمة دالة إنشاء، فإن useRef مطلوبة للحفاظ على كائن قابل للتغيير يدوم طوال فترة عمل المكوِّن. ينشئ المقتطف التالي خريطة ضمن هوك useEffect في النص الأساسي للمكوّن 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} />

توسيع مكوِّن الخريطة مع لوازم موقع إضافية

يمكن توسيع مكوِّن الخريطة الأساسي باستخدام لوازم إضافية لخيارات الخريطة، وأجهزة معالجة الأحداث، والأنماط التي يتم تطبيقها على عنصر 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]);

تتطلب أدوات معالجة الأحداث رمزًا أكثر تعقيدًا قليلاً لمحو المستمعين الحاليين عند تحديث المعالج كدعائم.

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

استكشاف الرمز

يمكن استكشاف نموذج الشفرة بالكامل عبر ملاعب الرموز عبر الإنترنت أدناه أو عن طريق نسخ مستودع 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