Привет всем! Сегодняшняя цель — построить простое приложение для поиска погоды, используя Vue 3 + OpenWeatherMap API.
Это — перевод оригинальной статьи с Medium.com на английском языке. Автор — William Schulte. Далее ведётся повествование от лица автора.
В ходе этого проекта мы рассмотрим:
- Как настроить функцию search() для обработки ответа от OpenWeatherMap API на основе введенного пользователем почтового индекса США. (В вашем случае, почтовый индекс может быть каким угодно).
- Как использовать метод 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
мы должны увидеть эти значения, преобразованные в Фаренгейт.
Теперь запустите приложение в браузере. На этом этапе вы должны увидеть такое окно:
Кажется, что все работает нормально. Однако одна из задач этого проекта — возвращать значения погоды, округленные до ближайшего целого числа. Для этого мы используем метод 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>
Конечный результат должен быть похож на первый снимок экрана:
И вот, у вас всё получилось! Поздравляю вас!