Приложение для поиска погоды с Vue 3 + OpenWeatherMap API

Рассказываем, как написать простое приложение для поиска погоды, используя Vue 3 и OpenWeatherMap API. Весь код — внутри.

2К открытий4К показов

Привет всем! Сегодняшняя цель – построить простое приложение для поиска погоды, используя Vue 3 + OpenWeatherMap API. 

Это — перевод оригинальной статьи с Medium.com на английском языке. Автор — William Schulte. Далее ведётся повествование от лица автора.

В ходе этого проекта мы рассмотрим:

  1. Как настроить функцию search() для обработки ответа от OpenWeatherMap API на основе введенного пользователем почтового индекса США. (В вашем случае, почтовый индекс может быть каким угодно).
  2. Как использовать метод JavaScript Object.keys() в функции для округления значений погоды (возвращаемых из ответа) до ближайшего целого числа.

Приложение, которое мы создадим, на самом деле является урезанной, одностраничной версией приложения OpenWeatherMap API dashboard, которое я создал ранее. Если хотите, вы можете посмотреть его репозиторий на GitHub. Для этого проекта мы создадим кое-что попроще.

Давайте начнем!

Настройка проекта

Сначала откройте IDE и скопируйте и вставьте следующий код шаблона в HTML-файл:

			<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<div id="demo" class="min-h-full bg-gray-200">
  <div class="bg-gray-300 flex justify-center">
    <div class="w-full max-w-md">
      <div class="space-y-6 m-8">
        <div class="rounded-md shadow-sm">
          <div>
              <input @keypress="search" v-model="cityZip" type="text" pattern="\d*" maxlength="5" 
              class="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm" placeholder="Enter Zip Code Here..." />
          </div>
        </div>
      </div>
    </div>
  </div>

  <div class="mx-auto max-w-4xl px-6">
    <div class="text-left">        
      <div class="p-6">
        <div class="p-12 bg-white shadow overflow-hidden sm:rounded-lg">
          <div>
            <h2 v-else class="text-4xl font-extrabold tracking-tight text-gray-900">{{ cities || '?' }}</h2>
            <p class="mt-2">{{ dateTime || '?' }}</p>
            <dl class="mt-8 grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 sm:gap-y-10 lg:gap-x-8">
              <div class="border-t border-gray-200 pt-4">
                  <dt class="font-large text-md text-gray-500">Temp</dt>
                  <dd class="mt-2 font-medium text-7xl text-gray-900">{{ weatherData.temp || '?' }}undefined#176;F</dd>
              </div>
              <div class="border-t border-gray-200 pt-4">
                  <dt class="font-medium text-md text-gray-500">Max/Min</dt>
                  <dd class="mt-2 text-4xl text-gray-900">{{ weatherData.temp_max || '?' }}undefined#176;F/ {{ weatherData.temp_min || '?' }}undefined#176;F</dd>
              </div>
              <div class="border-t border-gray-200 pt-4">
                  <dt class="font-medium text-md text-gray-500">Feels Like</dt>
                  <dd class="mt-2 text-4xl text-gray-900">{{ weatherData.feels_like || '?' }}undefined#176;F</dd>
              </div>
              <div class="border-t border-gray-200 pt-4">
                  <dt class="font-medium text-md text-gray-500">Humidity</dt>
                  <dd class="mt-2 text-4xl text-gray-900">{{ weatherData.humidity || '?' }}undefined#37;</dd>
              </div>
            </dl>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
		

Теперь создадим блок скрипта:

			<script>
    const { onMounted, ref, computed } = Vue
    const app = Vue.createApp({
    
    setup() {
      // Add Functionality Here
    }
})
app.mount('#demo')
</script>
		

Внутри функции setup() добавьте следующие свойства:

			setup() {
  const cities = ref()
  const weatherData = ref({})
  const dateTime = ref(null)
  const cityZip = ref('')

//Add search() function Here

  return { cities, weatherData, dateTime, cityZip, search }
}
		

Функция Search()

Чуть ниже свойств, которые мы написали, настроим функцию search(), которая будет обрабатывать вызов API OpenWeatherMap:

			const search = async (e) => {
    if (e.key == "Enter") {
        try {
            const res = await axios.get(
            `https://api.openweathermap.org/data/2.5/weather?zip=${cityZip.value},USundefinedappid={addyouropenweatherapikeyhere}undefinedunits=imperial`
            )
            cities.value = res.data.name
            weatherData.value = res.data.main
            const date = Date(res.data.dt).toLocaleString("en-US")
            dateTime.value = date.toString()
            cityZip.value = ''
        } catch (error) {
            console.log(error, 'error found!')
        }
    }
}
		

Чтобы вызов API работал, вам нужно зарегистрироваться для получения ключа API на сайте OpenWeatherMap. После создания учетной записи и получения ключа вставьте его в URL-адрес API в указанном месте.

После ввода 5-значного почтового индекса и нажатия кнопки Enter, API вернет данные о погоде для введенного пользователем почтового индекса (см. интерполированное свойство cityZip.value в URL).

Чтобы увидеть разобранные данные, просто скопируйте и вставьте URL-адрес с ключом API и введенным почтовым индексом в браузер. После этого вы увидите большое JSON-дерево погодных данных для выбранного города.

В данном приложении нам важно получить из ответа API данные о погоде, содержащиеся в следующем объекте:

			"main": {
    "temp": 10.67,
    "feels_like": -1.93,
    "temp_min": 9.28,
    "temp_max": 12.25,
    "pressure": 1038,
    "humidity": 55
},
		

Хотя по умолчанию эти значения представлены в градусах Цельсия, мы можем преобразовать их в градусы Фаренгейта, добавив "&units=imperial" в конце URL API. После сохранения res.data.main в weatherValue.data мы должны увидеть эти значения, преобразованные в Фаренгейт.

Теперь запустите приложение в браузере. На этом этапе вы должны увидеть такое окно:

Приложение для поиска погоды с Vue 3 + OpenWeatherMap API 1

Кажется, что все работает нормально. Однако одна из задач этого проекта – возвращать значения погоды, округленные до ближайшего целого числа. Для этого мы используем метод Object.keys() как часть функции для округления значений погоды. Но сначала давайте рассмотрим основную концепцию метода Object.keys().

Метод Object.keys()

Если вы не знали, метод Object.keys() в JavaScript возвращает массив перечисляемых строковых имен свойств данного объекта. Обратите внимание на следующий пример из документации JavaScript MDN:

			const object1 = {
  a: 'somestring',
  b: 42,
  c: false
};

console.log(Object.keys(object1));
		

object1 – это объект, содержащий свойства. Каждое свойство представлено парой ключ:значение. Результатом является новый массив, содержащий только ключи исходного объекта. В данном случае результатом будет:

			> Array ["a", "b", "c"]
		

Теперь давайте применим эту концепцию к нашему приложению. Вернемся в наш HTML-файл и добавим код ниже функции search():

			const roundedValue = () => {
    const roundData = {}
    Object.keys(weatherData.value).map(key => {
        console.log(key)
        return roundData[key] = Math.round(weatherData.value[key]);                
    })
    return roundData
}
		

Внутри функции roundedValue() мы создадим свойство roundData, которое установим в пустой объект. Затем мы используем метод Object.keys() для создания нового массива, содержащего все ключи из weatherData.value.

console.log(Object.keys(weatherData.value)) должен вернуть следующее:

			['temp', 'feels_like', 'temp_min', 'temp_max', 'pressure', 'humidity']
		

Далее мы используем метод map() для итерации по вновь созданному массиву ключей, причем каждый элемент в массиве ключей будет представлен элементом key.

Чтобы округлить каждое значение погоды, мы передадим weatherData.value[key] в Math.round() внутри обратного вызова, используя при этом ключевой элемент как индекс для каждого итерируемого элемента weatherData.value.

Наконец, каждое новое округленное значение мы присвоим roundData[key].

После выполнения console.log(roundData) мы должны увидеть новый объект:

			{
  temp: 22, 
  feels_like: 10, 
  temp_min: 19, 
  temp_max: 25, 
  pressure: 1040, 
  humidity: 37 
}
		

В функции setup() мы добавим новое вычисляемое свойство roundedData, которое будет возвращать наша функция roundedValue(). Не забудем включить оба этих элемента в возвращаемую функцию setup.

			setup() {
  const roundedData = computed(() => roundedValue())
  const cities = ref()
  const weatherData = ref({})
  const dateTime = ref(null)
  const cityZip = ref('')

//...search() function

//...roundedValue() function

  return { cities, weatherData, dateTime, cityZip, search, roundedData, roundedValue }
}
		

Давайте также обновим карточку с информацией о погоде в шаблоне, чтобы интерполировать вычисляемое свойство roundedData (там, где это необходимо):

			<div class="mx-auto max-w-4xl px-6">
  <div class="text-left">        
    <div class="p-6">
      <div class="p-12 bg-white shadow overflow-hidden sm:rounded-lg">
        <div>
          <h2 v-else class="text-4xl font-extrabold tracking-tight text-gray-900">{{ cities || '?' }}</h2>
          <p class="mt-2">{{ dateTime || '?' }}</p>
          <dl class="mt-8 grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 sm:gap-y-10 lg:gap-x-8">
            <div class="border-t border-gray-200 pt-4">
                <dt class="font-large text-md text-gray-500">Temp</dt>
                <dd class="mt-2 font-medium text-7xl text-gray-900">{{ roundedData.temp || '?' }}undefined#176;F</dd>
            </div>
            <div class="border-t border-gray-200 pt-4">
                <dt class="font-medium text-md text-gray-500">Max/Min</dt>
                <dd class="mt-2 text-4xl text-gray-900">{{ roundedData.temp_max || '?' }}undefined#176;F/ {{ roundedData.temp_min || '?' }}undefined#176;F</dd>
            </div>
            <div class="border-t border-gray-200 pt-4">
                <dt class="font-medium text-md text-gray-500">Feels Like</dt>
                <dd class="mt-2 text-4xl text-gray-900">{{ roundedData.feels_like || '?' }}undefined#176;F</dd>
            </div>
            <div class="border-t border-gray-200 pt-4">
                <dt class="font-medium text-md text-gray-500">Humidity</dt>
                <dd class="mt-2 text-4xl text-gray-900">{{ roundedData.humidity || '?' }}undefined#37;</dd>
            </div>
          </dl>
        </div>
      </div>
    </div>
  </div>
</div>
		

Окончательный код должен выглядеть следующим образом:

			<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<div id="demo" class="min-h-full bg-gray-200">
    <div class="bg-gray-300 flex justify-center">
        <div class="w-full max-w-md">
            <div class="space-y-6 m-8">
                <div class="rounded-md shadow-sm">
                    <div>
                        <input @keypress="search" v-model="cityZip" type="text" pattern="\d*" maxlength="5" 
                        class="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm" placeholder="Enter Zip Code Here..." />
                    </div>
                </div>
            </div>
        </div>
    </div>

    <div class="mx-auto max-w-4xl px-6">
        <div class="text-left">        
            <div class="p-6">
                <div class="p-12 bg-white shadow overflow-hidden sm:rounded-lg">
                    <div>
                        <h2 v-else class="text-4xl font-extrabold tracking-tight text-gray-900">{{ cities || '?' }}</h2>
                        <p class="mt-2">{{ dateTime || '?' }}</p>
                        <dl class="mt-8 grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 sm:gap-y-10 lg:gap-x-8">
                        <div class="border-t border-gray-200 pt-4">
                            <dt class="font-large text-md text-gray-500">Temp</dt>
                            <dd class="mt-2 font-medium text-7xl text-gray-900">{{ roundedData.temp || '?' }}undefined#176;F</dd>
                        </div>
                        <div class="border-t border-gray-200 pt-4">
                            <dt class="font-medium text-md text-gray-500">Max/Min</dt>
                            <dd class="mt-2 text-4xl text-gray-900">{{ roundedData.temp_max || '?' }}undefined#176;F/ {{ roundedData.temp_min || '?' }}undefined#176;F</dd>
                        </div>
                        <div class="border-t border-gray-200 pt-4">
                            <dt class="font-medium text-md text-gray-500">Feels Like</dt>
                            <dd class="mt-2 text-4xl text-gray-900">{{ roundedData.feels_like || '?' }}undefined#176;F</dd>
                        </div>
                        <div class="border-t border-gray-200 pt-4">
                            <dt class="font-medium text-md text-gray-500">Humidity</dt>
                            <dd class="mt-2 text-4xl text-gray-900">{{ roundedData.humidity || '?' }}undefined#37;</dd>
                        </div>
                        </dl>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    const { onMounted, ref, computed } = Vue
    const app = Vue.createApp({
    
    setup() {
        const roundedData = computed(() => roundedValue())
        const cities = ref()
        const weatherData = ref({})
        const dateTime = ref(null)
        const cityZip = ref('')
        
        const search = async (e) => {
            if (e.key == "Enter") {
                try {
                    const res = await axios.get(
                    `https://api.openweathermap.org/data/2.5/weather?zip=${cityZip.value},USundefinedappid={addyouropenweatherapikeyhere}undefinedunits=imperial`
                    )
                    cities.value = res.data.name
                    weatherData.value = res.data.main
                    const date = Date(res.data.dt).toLocaleString("en-US")
                    dateTime.value = date.toString()
                    cityZip.value = ''
                } catch (error) {
                    console.log(error, 'error found!')
                }
            }
        }

        const roundedValue = () => {
            const roundData = {}
            console.log(Object.keys(weatherData.value))
            Object.keys(weatherData.value).map(key => {
                console.log(Math.round(weatherData.value[key]))
                return roundData[key] = Math.round(weatherData.value[key]);                
            })
            return roundData
        }

        return {
            cities,
            weatherData,
            dateTime,
            cityZip,
            search,
            roundedData,
            roundedValue
        }
    }
})
app.mount('#demo')
</script>
		

Конечный результат должен быть похож на первый снимок экрана:

Приложение для поиска погоды с Vue 3 + OpenWeatherMap API 2

И вот, у вас всё получилось! Поздравляю вас! 

Следите за новыми постами
Следите за новыми постами по любимым темам
2К открытий4К показов