در جلسه قبل با استفاده از کتابخانه axios درخواست خود را به گوگل ارسال کردیم و نتیجه آن را نیز دیدیم. سپس این نتیجه را در یک متغیر ذخیره کردیم اما به این بحث رسیدیم که autocompletion ای وجود ندارد. راه حل آن را در جلسه قبل به صورت معما برایتان باقی گذاشتم که در این جلسه به پاسخش می رسیم؛ type alias ها راه حل ما هستند:
axios.get<{results: {geometry: {location: {lat: number, lang: number}}}[]}>( `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURI(enteredAddress)}&key=${GOOGLE_API_KEY}`) .then(response => { const coordinates = response.data.results[0].geometry.location; }) .catch(err => { console.log(err); });
در اینجا به تایپ اسکریپت گفته ایم که متد get پاسخی دارد که نوعش آرایه ای از اشیاء است:
{}[]
هر کدام از این اشیاء results را در خود خواهد داشت که خودش دارای geometry و خود geometry دارای location و location دارای دو خصوصیت lang و lat می باشد که هر دو عدد (number) هستند. طبیعتا انجام این کار الزامی نیست اما به ما کمک می کند تا از خطاها دور باشیم.
البته برای اینکه کدهایمان شلوغ نشود من آن را از این قسمت برداشته و به شکل زیر می نویسم تا به طور کامل یک type alias بشود:
type GoogleGeocodingResponse = { results: { geometry: { location: { lat: number; lng: number } } }[]; status: "OK" | "ZERO_RESULTS"; }; function searchAddressHandler(event: Event) { event.preventDefault(); const enteredAddress = addressInput.value; axios.get<GoogleGeocodingResponse>( `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURI(enteredAddress)}&key=${GOOGLE_API_KEY}`) .then(response => { const coordinates = response.data.results[0].geometry.location; }) .catch(err => { console.log(err); }); }
همانطور که می بینید، علاوه بر مشخص کردن تایپ results، یک فیلد دیگر به نام status را نیز تعریف کرده ام که یا OK (همان 200) خواهد بود و یا مقدار دیگری. گوگل در مورد status های برگردانده شده توضیح می دهد بنابراین اگر دوست دارید به همان لینک مراجعه کنید. در واقع status می تواند حالت های مختلفی را داشته باشد که همه آن ها برای من مهم نیستند. برای من OK مهم است و ZERO_RESULTS. کد دوم (ZERO_RESULTS) زمانی رخ می دهد که گوگل هیچ نتیجه ای برای آدرس پیدا نکند. بنابراین فقط همین دو مورد را درون تایپ خودم قرار داده ام:
type GoogleGeocodingResponse = { results: { geometry: { location: { lat: number; lng: number } } }[]; status: "OK" | "ZERO_RESULTS"; };
حالا می توانیم کد خود را تکمیل کنیم:
axios.get<GoogleGeocodingResponse>( `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURI(enteredAddress)}&key=${GOOGLE_API_KEY}`) .then(response => { if (response.data.status !== "OK") { throw new Error("Could not fetch location!"); } const coordinates = response.data.results[0].geometry.location; }) .catch(err => { console.log(err); });
یعنی اگر status ما برابر با OK نبود (گوگل هیچ آدرسی را پیدا نکرد) یک خطا پرتاب شود. توجه کنید که خطاهای درون catch خطاهای سرور هستند (مثلا کاربر ما در ایران است و به دلیل تحریم، درخواستش به گوگل ارسال نمی شود یا اینکه خود ما آدرس URL درخواست را به اشتباه ثبت کرده ایم) اما بررسی status به معنی پاسخ گوگل است که می تواند با نتیجه خاص یا بدون نتیجه باشد (مثلا چنین آدرسی اصلا وجود نداشته باشد که یعنی خطا از سمت سرور نیست).
مرحله بعد نمایش آدرس روی نقشه google maps است. برای این کار می توانید عبارت google js maps را در گوگل جست و جو کنید یا به راحتی به لینک زیر بروید:
https://developers.google.com/maps/documentation/javascript/tutorial
این لینک شما را به documentation رسمی گوگل مپس برای زبان جاوا اسکریپت می برد. در همان ابتدا کد زیر را در این صفحه خواهید دید:
<!DOCTYPE html> <html> <head> <title>Simple Map</title> <meta name="viewport" content="initial-scale=1.0"> <meta charset="utf-8"> <style> /* Always set the map height explicitly to define the size of the div * element that contains the map. */ #map { height: 100%; } /* Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div id="map"></div> <script> var map; function initMap() { map = new google.maps.Map(document.getElementById('map'), { center: {lat: -34.397, lng: 150.644}, zoom: 8 }); } </script> <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap" async defer></script> </body> </html>
این کد به طور خلاصه نحوه استفاده از google maps را به ما نشان می دهد. من همین کد را روی پروژه خودمان پیاده سازی می کنم تا طبق documentation پیش رفته باشیم. در ابتدا باید تگ script بالا را در صفحه index.html خود قرار بدهیم:
// بقیه کدها // <title>Understanding TypeScript</title> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCIaAc2c5M3VpbCH6PPq_guwy9lHuowXOs" async defer ></script> <script src="dist/bundle.js" defer></script> <link rel="stylesheet" href="app.css" /> </head> <body> <div id="map"> // بقیه کدها //
همانطور که می بینیم API KEY خودم را با قسمت API KEY قالب آن عوض کرده ام. شما هم باید همین کار را انجام بدهید. همچنین قسمت انتهای این اسکریپت (&callback=initMap) را حذف کرده ام. در مثال documentation این قسمت باعث می شود متدی به نام initMap به محض بارگذاری اسکریپت، در صفحه نمایش داده شود اما من چنین چیزی را نمی خواهم. چرا؟ به دلیل اینکه ما ابتدا آدرس را از کاربر می گیریم و سپس آن را به گوگل می دهیم تا مشخص شود که چنین آدرسی وجود دارد یا خیر. بنابراین چطور می توانیم به محض بارگذاری صفحه HTML، نقشه را به کاربر نمایش دهیم؟ این قابلیت برای صفحات کسب و کار مناسب است که می خواهند به محض باز شدن صفحه، محل کسب و کارشان را به کاربر نمایش بدهند.
حالا اگر به کد استفاده شده در documentation دوباره نگاه کنیم، متوجه می شویم که ساختن یک نقشه بسیار ساده است:
function initMap() { map = new google.maps.Map(document.getElementById('map'), { center: {lat: -34.397, lng: 150.644}, zoom: 8 }); }
یعنی از کلاس Map یک نمونه ساخته و به عنوان پارامتر ورودی 2 چیز را پاس داده ایم:
بنابراین باید همین کد را در پروژه خودمان (app.ts) کپی کنیم؟ درست است؟ بله حرفتان درست است اما مشکلی وجود دارد. کد بالا از new google استفاده می کند و google در نهایت به صورت یک متغیر سراسری در دسترس ما خواهد بود اما تایپ اسکریپت که این مسئله را نمی داند! بنابراین از google.maps خطا خواهد گرفت. قبلا در مورد راه حل این مشکل صحبت کرده بودیم:
import axios from "axios"; const form = document.querySelector("form")!; const addressInput = document.getElementById("address")! as HTMLInputElement; const GOOGLE_API_KEY = "AIzaSyCIaAc2c5M3VpbCH6PPq_guwy9lHuowXOs"; declare var google: any;
با این کار به تایپ اسکریپت گفته ایم که google یک متغیر سراسری خواهد بود و استفاده از آن مشکلی ندارد. من کدهای دیگر را نیز آورده ام تا بدانید دستور declare را در کجا قرار دهیم. بنابراین کدهایمان را نیز می نویسیم:
function searchAddressHandler(event: Event) { event.preventDefault(); const enteredAddress = addressInput.value; axios .get<GoogleGeocodingResponse>( `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURI( enteredAddress )}&key=${GOOGLE_API_KEY}` ) .then(response => { if (response.data.status !== "OK") { throw new Error("Could not fetch location!"); } const coordinates = response.data.results[0].geometry.location; const map = new google.maps.Map(document.getElementById("map"), { center: coordinates, zoom: 16 }); new google.maps.Marker({ position: coordinates, map: map }); }) .catch(err => { alert(err.message); console.log(err); }); }
همانطور که می بینید من center را برابر coordinates قرار داده ام که همان مختصات دریافتی خودمان است و zoom را نیز روی 16 گذاشته ام. در نهایت از یک marker استفاده کرده ام که یک شیء با دو خصوصیت می گیرد: مختصات و map ای که ساخته ایم. marker همان علامتی است که محل دقیق یک نقطه را روی نقشه مشخص می کند (شبیه به آیکون GPS). حالا می توانید کدها را ذخیره کرده و به مرورگ بروید. با وارد کردن یک آدرس ساده، می بینید که برنامه بدون مشکل کار می کند.
البته هنوز مسئله ای باقی می ماند. declare کردن google باعث رفع خطا می شود اما دیگر type support دریافت نمی کنیم. مثلا اگر به جای google.maps.Marker بگوییم google.map.Marker تایپ اسکریپت از ما خطا نخواهد گرفت. به نظر شما چطور می توان این مشکل را حل کرد؟
راه حل ما استفاده از types است که قبلا در موردش صحبت کرده بودیم. با جست و جوی عبارت types google maps با انواع پکیج های مختلف برای اضافه کردن تایپ به کتابخانه گوگل مپس آشنا می شوید. مثلا لینک یکی از این موارد را برایتان قرار می دهم:
https://www.npmjs.com/package/@types/googlemaps
ما می توانیم به راحتی دستور زیر را در ترمینال پروژه خود اجرا کنیم تا این پکیج نصب شود:
npm install --save-dev @types/googlemaps
حالا نیازی به declare نداریم بنابراین آن را کامنت می کنم:
// declare var google: any;
از این به بعد autocompletion و type support دارم و بدون خطا می توانم از آن استفاده کنم.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.