Small Grey Outline Pointer React :: 날씨 앱 만들기
본문 바로가기
Dev./React

React :: 날씨 앱 만들기

by sso. 2022. 8. 29.

https://openweathermap.org/api

 

Weather API - OpenWeatherMap

Please, sign up to use our fast and easy-to-work weather APIs. As a start to use OpenWeather products, we recommend our One Call API 3.0. For more functionality, please consider our products, which are included in professional collections.

openweathermap.org

 

날씨 api 가져오기

회원 가입 후 사용한다

무료로 사용 가능한 api

 

 

회원가입 후 프로필에 들어가면 api 키가 나온다

 

로직

//1. 앱이 실행 되자 마자 현재 위치 기반의 날씨가 보인다
//2. 지금 현재 도시와 온도 , 날씨 상태 정보가 나온다
//3. 하단에 도시 버튼 5개 (현재위치, 다른 도시 4개)
//4. 버튼을 누를 때 마다 해당 되는 도시별 날씨가 보여진다
//5. 현재위치 버튼을 누르면 다시 현재위치 기반의 날씨 정보를 보여준다
//6. 데이터를 들고오는 동안 로딩 스피너가 돌아간다

 

 

 

위치 정보 가져오기

 

https://www.w3schools.com/html/html5_geolocation.asp

 

HTML Geolocation API

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

 

 

import { useEffect, useState } from 'react';
import './App.css';

//1. 앱이 실행 되자 마자 현재 위치 기반의 날씨가 보인다
//2. 지금 현재 도시와 섭씨/화씨 , 날씨 상태 정보가 나온다
//3. 하단에 도시 버튼 5개 (현재위치, 다른 도시 4개)
//4. 버튼을 누를 때 마다 해당 되는 도시별 날씨가 보여진다
//5. 현재위치 버튼을 누르면 다시 현재위치 기반의 날씨 정보를 보여준다
//6. 데이터를 들고오는 동안 로딩 스피너가 돌아간다
function App() {
    const getCurrentLocation = () => {
        navigator.geolocation.getCurrentPosition((position) => {
            let lat = position.coords.latitude;
            let lon = position.coords.longitude;
            console.log('현재 위도 경도는?', lat, lon);
        });
    };
    useEffect(() => {
        getCurrentLocation();
    }, []);
    return <div>안녕텍스트</div>;
}

export default App;

 


 

날씨 api 들고오기

url 들고오기

 

 

api 호출 함수 만들기

    const getWeatherByCurrentLocation = async (lat, lon) => {
        let url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=81523c3aa0ea13bf7e0d71967cd5d5d4`;
        let response = await fetch(url);
        let data = await response.json();
        console.log('데이터 확인!', data);
    };

 

콘솔창에 호출한 api의 data가 나온다

 

 

리액트 부트스트랩 사용하기 (버튼 만들어주기)

 

https://react-bootstrap.github.io/getting-started/introduction

 

React-Bootstrap

The most popular front-end framework, rebuilt for React.

react-bootstrap.github.io

 

터미널에서 설치하기

npm install react-bootstrap bootstrap

 

 

사용하고 싶은 컴포넌트 파일 상단에 import 걸어주기

import { Button } from 'react-bootstrap';

 

css

 

 

 

사용하고 싶은 버튼 속성 가져오기

 

 

 

 


 

Weather.box.js

import React from 'react';

const WeatherBox = ({ weather }) => {
    console.log('웨더 정보 확인', weather);
    return (
        <div className="weather-box">
            <div>{weather.name}</div>
            <h2>30도 / 130화씨</h2>
            <h3>흐림</h3>
        </div>
    );
};

export default WeatherBox;

 

{다이나믹 값} 으로 웨더 데이터에서 정보를 가져 온다

 

 

 

 

섭씨온도로 확인하기 위해서 추가할 것들

 

 

let url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=81523c3aa0ea13bf7e0d71967cd5d5d4&units=metric`;

url 뒤에 &units=metric 을 추가해준다

혹은 이렇게 추가하지 않고 섭씨/화씨 온도를 각각 따로 처리 해도 된다

 

 

섭씨로 표기 된 데이터를 바탕으로 화씨 온도를 표시해 주는 방법

import React from 'react';

const WeatherBox = ({ weather }) => {
    console.log('웨더 정보 확인', weather);
    return (
        <div className="weather-box">
            <div>{weather?.name}</div> {/* weather가 참이면 실행 (삼항연산자의 또다른 표기법) */}
            <h2>
                {weather?.main.temp}℃ / {((weather?.main.temp * 9) / 5 + 32).toFixed(2)}℉
            </h2>
            <h3>흐림</h3>
        </div>
    );
};
/* 섭씨를 화씨로 변환하기 (0°C × 9/5) + 32 = 32°F */
export default WeatherBox;

 

 

 


 

도시 추가하기 array를 만들어 관리한다
const cities = ['paris', 'new york', 'tokyo', 'seoul'];

 

import React from 'react';
import { Button } from 'react-bootstrap';

const WeatherButton = ({ cities }) => {
    console.log('도시정보 확인', cities);
    return (
        <div>
            <Button variant="warning">Current Location</Button>
            {cities.map((item) => (
                <Button variant="warning">{item}</Button>
            ))}
        </div>
    );
};

export default WeatherButton;

배열에 있는 도시들이 버튼으로 생성 됐다

 

 

 

리액트는 one-way 구조이다

app(부모)에 모든 정보를 넣어 놓고 자식들에게 정보를 나눠주는 형식

WeatherButton.js 에서 버튼 클릭시 해당 국가의 데이터를 app.js 로 바로 넘겨줄 수 없다

 

 

 

 

WeatherButton.js

import React from 'react';
import { Button } from 'react-bootstrap';

const WeatherButton = ({ cities, setCity }) => {
    console.log('도시정보 확인', cities);
    return (
        <div>
            <Button variant="warning">Current Location</Button>
            {cities.map((item) => (
                <Button variant="warning" onClick={() => setCity(item)}>
                    {item}
                </Button>
            ))}
        </div>
    );
};

export default WeatherButton;

 

 


 

 

 

 


로딩 스피너 만들기

 

https://www.npmjs.com/package/react-spinners

 

react-spinners

A collection of react loading spinners. Latest version: 0.13.4, last published: a month ago. Start using react-spinners in your project by running `npm i react-spinners`. There are 669 other projects in the npm registry using react-spinners.

www.npmjs.com

 

npm install --save react-spinners

설치 후

 

app.js 상단에 임포트 걸기

import ClipLoader from "react-spinners/ClipLoader";

 

 

로딩스피너

 

 

 

 

 


 

오늘 날짜 추가
const todayData = () => {
        const week = ['월','화','수','목','금','토','일'];
        let now = new Date();
        let todayMonth = (now.getMonth()+1) > 9 ? (now.getMonth()+1) : (now.getMonth()+1);
        let todayDate = now.getDate() > 9 ? now.getDate() : '0' + now.getDate();
        let dayOfWeek = week[now.getDay()];
        return todayMonth + '월 ' + todayDate + '일 ' + dayOfWeek + '요일'
    }

 

 

날씨 아이콘 추가하기

 

 

open API 에 있는 날씨 아이콘을 살펴본다

https://openweathermap.org/weather-conditions

 

Weather Conditions - OpenWeatherMap

Weather Conditions Home Weather Conditions

openweathermap.org

 

id 값에 따라 날씨가 다르게 표시 되어 있다

 

 

App.js

 

 

WeatherBox.js 에 와서 props 로 id를 던져준다

const WeatherBox = ({weather, id})

 

 

icon URL 주소를 가지고 온다

<img className="img-fluid" src = {'http://openweathermap.org/img/wn/{icon}@2x.png'} />

 

 

 

 

아직 엑박으로 나오고 있지만 콘솔창에 확인해보면 id 값이 맞게 출력 되고 있다

이제 엑박을 어떻게 이미지로 나올 수 있게 수정하는 일만 남았다!

 

 

 


 

에러 해결

icon URL 주소가 잘못 되어 있어서 엑박으로 뜨게 된 것이었다

 

 

수정 전

<img className="img-fluid" src = {'http://openweathermap.org/img/wn/{icon}@2x.png'} />

 

수정 후 동적 값으로 주기 위해 $를 붙여 줬다

<img className="img-fluid" src={`http://openweathermap.org/img/wn/${icon}@2x.png`} />

 

 

 

아이디와 아이콘 값이 잘 출력 되고 그에 맞게 아이콘도 그림으로 잘 나오게 됐다

근데 get 404 에러가 떠있어서 추후에 다시 수정해야겠다

 

 

 

 


수정하면서 추가 된 내용들

 

https://uigradients.com/#Horizon

 

uiGradients - Beautiful colored gradients

uiGradients is a handpicked collection of beautiful color gradients for designers and developers.

uigradients.com

배경색 그라데이션 

 

 

 

 {Math.round(weather?.main.temp)}º

Math.round 함수 사용하여 소수점 아래 다 버리기

 

 

검색 기능 추가하기

 

 

const [query, setQuery] = useState('');

 

App.js

/* 검색 기능 */
    const search = async (e) => {
            if(e.key === "Enter"){
                fetch(`https://api.openweathermap.org/data/2.5/weather?q=${query}&appid=${API_KEY}&units=metric`)
                .then(res => res.json())
                .then(result => {
                    setWeather(result);
                    setQuery('');
                    //console.log("result 확인", result);
                })
            }
    }

검색창에 도시를 입력하고 엔터를 누르면 

url에 동적값으로 도시 이름이 들어가게 된다 ${query}

 

 

 

 

기온에 따라 배경 색상 바뀌게 하기
className={(typeof weather.main != "undefined")
? ((weather.main.temp > 26)
    ? 'container_warm' : 'container') : 'container'}

기온이 26도 이상일 때 container의 클래스네임을 다르게 부여하여 색상을 바꿔준다

 

 

 

 

 

하단의 버튼에 없는 도시들도 검색하면 바로 정보가 나오고 26도 이상일 때 배경색이 바뀐다

 

 

 


 

며칠 지나니까 에러 뜨고 데이터가 넘겨지는게 안되어서 코드 수정 했다

 

현재 위치 정보가 아예 뜨지 않는 상태였음

 

 

 

수정 전

/* 현재 위치의 위도 경도 가져오기 */
    const getCurrentLocation = () => {
        navigator.geolocation.getCurrentPosition((position) => {
            let lat = position.coords.latitude;
            let lon = position.coords.longitude;
            //console.log('현재 위도 경도는?', lat, lon);
            getWeatherByCurrentLocation(lat, lon);
        });
    };

 

수정 후

/* 현재 위치의 위도 경도 가져오기 */
    const getCurrentLocation = () => {
        navigator.geolocation.getCurrentPosition((position) => {
            const { latitude, longitude } = position.coords;
            getWeatherByCurrentLocation(latitude, longitude);
        });
    };
  • 변수 수정

 

 

 

수정 전

/* 현재 위치의 날씨 정보 */
    const getWeatherByCurrentLocation = async (lat, lon) => {
        let url = `https://api.openweathermap.org/data/2.5/weather?q=lat=${lat}&lon=${lon}&appid=${API_KEY}&units=metric`;
        let response = await fetch(url);
        let data = await response.json();
        //console.log('데이터 확인!', data);
        setWeather(data);
        setId(data.weather[0].id);
        setIcon(data.weather[0].icon);
        setLoading(false); //스피너 종료
    };

 

 

수정 후

/* 현재 위치의 날씨 정보 */
    const getWeatherByCurrentLocation = async (lat, lon) => {
        try {
            let url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}`;
            let response = await fetch(url);
            let data = await response.json();
            setWeather(data);
            setIcon(data.weather[0].icon);
            setLoading(false);
        } catch (err) {
            setError(err.message);
            setLoading(false);
        }
    };
  • try-catch 사용해서 에러처리 

 

 

 

수정 전

    useEffect((city) => {
        /* useEffect는 componentDidUpdate 역할도 한다 */
        /* useEffect 는 한 곳에서 정리한다 */
        if (city === '') {
            setLoading(true);
            getCurrentLocation();
        } else {
            setLoading(true);
            getWeatherByCity();
        }
    }, [city]);

수정 후

    useEffect(() => {
        if (city === '') {
            setLoading(true);
            getCurrentLocation();
        } else {
            setLoading(true);
            getWeatherByCity();
        }
    }, [city]);

city 인자 넘겨받지 않고 뺌

 

 

 

수정 전

return (
        /* UI를 보여주는 곳 */
        <div>
            {/* 스피너 true일 때의 조건 */}
            {loading ? (
                <div className="container">
                    <ClipLoader color="#ff0000" loading={loading} size={100} />
                </div>
            ) : (
                <div className={(typeof weather.main != "undefined")
                ? ((weather&&weather.main.temp > 26)
                    ? 'container_warm' : 'container') : 'container'}>
                    <div className='search_box'>
                        <input
                        type="text"
                        placeholder='Search...'
                        onChange={e=>setQuery(e.target.value)}
                        value={query}
                        onKeyPress={search}
                        >
                        </input>
                    </div>
                    <WeatherBox weather={weather} id={id} icon={icon}/> {/* props로 넘기기 */}
                    <WeatherButton cities={cities} selectCity={city} handleCityChange={handleCityChange} /> {/* setCity라는 함수를 props로 넘겨준다 */}
                </div>
            )}
        </div>
    );

수정 후

return (
        /* UI를 보여주는 곳 */
        <div>
            {/* 스피너 true일 때의 조건 */}
            {loading ? (
                <div className="container">
                    <ClipLoader color="#ff0000" loading={loading} size={100} />
                </div>
            ) : !error ? (
                <div className={typeof weather.main != 'undefined' ? (weather && weather.main.temp > 26 ? 'container_warm' : 'container') : 'container'}>
                    <div className="search_box">
                        <input type="text" placeholder="Search..." onChange={(e) => setQuery(e.target.value)} value={query} onKeyPress={search}></input>
                    </div>
                    <WeatherBox weather={weather} icon={icon} /> {/* props로 넘기기 */}
                    <WeatherButton cities={cities} selectCity={city} handleCityChange={handleCityChange} /> {/* setCity라는 함수를 props로 넘겨준다 */}
                </div>
            ) : (
                error
            )}
        </div>
    );

error 에러처리

 

 

 

 

 

728x90

댓글