외부활동/유데미X스나이퍼팩토리

[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 17일차 - 날씨 API, React 기초 이해, 컴포넌트 기반 아키텍쳐, 가상 DOM

주디_JUDI 2023. 6. 21. 17:47

날씨 API 받기

https://openweathermap.org/api

  • 해당 링크로 들어가서 회원가입 후 로그인하여 API 키를 받는다.

상단 바의 내 정보 중 내 api 키로 들어가기

  • 로그인 후 My API keys 창으로 이동한다.
  • 거기에 적혀있는 나의 API를 복사하여 사용한다.

나의 API 키

Navigator

브라우저에서 제공하는 요소 중에서는 navigator가 있다.

navigator 안에는 geolocation이라는 객체가 있고, 그 안에는 많은 메소드들이 존재한다.

콘솔에 찍은 navigator

getCurrentPosition

getCurrentPosition은 geolocation 안에 있는 콜백함수로 매개변수로 두 가지 다른 콜백함수를 받는다. 첫 번째 매개변수에서는 성공할 때 결과값(GeolocationPosition이라는 객체)을 반환하고, 두 번째 매개변수로는 실패했을 때 에러를 반환한다.

geolocationPosition

GeolocationPosition이라는 객체 안에도 여러 가지 키와 키값이 저장되어있다. 그 중에서 latitude와 longitude는 위도와 경도로, 이를 활용하여 현재 위치의 날씨 정보를 구할 수 있다.

 

API 키를 이용하여 날씨 API 부르는 방법

해당 홈페이지에서는 날씨 API를 부르는 방법도 공식 문서로 기록해두었다. 이에 대한 내용은 하단의 링크를 통해 들어가면 자세히 설명이 되어 있다.

https://openweathermap.org/current#geo

fetch

API를 불러오기 위해서는 fetch라는 전역 API 메소드를 사용해야한다. 위에서 제공해준 api를 이 fetch를 통해서 불러오고, api가 필수로 요구하는 값인 lat(위도), lon(경도), API_KEY(내가 받은 api 키값)을 변수로 저장해서 전달하면 날씨 API를 부르는 것까지는 성공이다.

 fetch(
   `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}`
   )

then

then 메서드는 Promise를 리턴하고 두 개의 콜백 함수를 인수로 받는다. 하나는 Promise가 이행했을 때, 다른 하나는 거부했을 때를 위한 콜백 함수이다.

.then(function (response) {
	// API 응답 이행값
     return response.json();
 })
.then(function (json) {
	// json 이행값
     console.log(json);
 });

response.json()

response.json()은 API 응답(response)의 값의 텍스트 형태를 Promise 형태로 변경해준다. response.json()를 사용하지 않으면 body의 데이터는 제대로 읽지 않은 채로 전달되어 데이터 오류가 발생한다. 그리고 리턴한 json 값은 또 Promise 이므로 따로 then으로 반환해야한다.

 

날씨 json 안의 정보들

  • json 안에는 clouds 라는 구름에 대한 정보, coord 안에는 경도와 위도에 대한 정보, main 안에는 습도, 압력, 온도 등의 정보가 담겨있다. 그 외에도 비와 바람에 대한 정보처럼 여러 가지 날씨 데이터를 읽을 수 있다.
  • 이 중에서 원하는 정보를 변수에 담아서 innerText로 html에 정보를 출력할 수 있는 것이다.

날씨 API 키 환경변수로 저장하기 (dotenv 없이!)

오픈 API에서 받아온 개인 API는 외부로 노출되면 해킹의 위험이 있다. 그러므로 해당 키는 환경변수에 꼭 저장해서 깃허브에 올리도록 해야한다.

 

1) js 파일을 생성한다.

2) 생성한 js 파일을 .gitignore 파일에 추가한다.

// 해당 js 파일을 최상단에 생성했을 경우
// .gitignore

*apikey.js

3) 해당 파일 안에 객체 형태로 API를 저장한다.

const config = {
	'apikey' = "내가 받은 API 키"
}

4) 이 API가 필요한 html의 script 구간에서 가장 최상단으로 해당 파일을 불러온다. (가장 맨위에서 먼저 불러와야 하므로)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>5-1.날씨 API</title>
  </head>
  <body>
    <h1 id="weather"></h1>
    <script src="../../apikey.js"></script>
    <script>...</script>
  </body>
</html>

5. API 키가 필요한 JS script 파일에 변수에 담아 사용한다.

const API_KEY = config.apikey;

React

React에 대하여

React는 페이스북에서 만든 자바스크립트 라이브러리이다. 자바스크립트의 특정 값이 바뀌면 특정 DOM의 속성이 바뀌도록 연결을 해주어서, 업데이트 하는 작업을 간소화해주는 방식으로 웹개발의 어려움을 해결한다. 

브라우저에 실제로 보여지는 DOM이 아니라 메모리에 가상으로 존재하는 DOM으로서 JavaScript 객체이기 때문에 작동 성능이 실제로 브라우저에서 DOM을 보여주는 것 보다 속도가 훨씬 빠르다.

왜 React를 사용해야 할까?

UI 컴포넌트를 만들고 재사용하는데 매우 용이하여 코드의 유지보수 측면에서 효율적이다. 즉, 리액트는 UI 컴포넌트를 만들고 만들어진 컴포넌트를 통해 사용자와 상호작용을 하고 화면을 업데이트 하는데 최적화 되어있어 많은 인기를 갖고 있다.

게다가 프론트엔드 개발자들 사이에서 가장 많이 사용하는 오픈소스 라이브러리이기 때문에 회사가 사라지지 않는 한에서는 꾸준한 관리와 업데이트를 기대할 수 있다는 장점도 있다.

컴포넌트 기반 아키텍쳐란?

리액트 앱은 컴포넌트로 만들어진다. 컴포넌트는 UI의 일부로, UI를 세밀하게 나눈 하나의 조각을 컴포넌트라고 해석이 가능하다. 즉, 컴포넌트는 버튼만큼 작을 수도 있고, 전체 페이지만큼 클 수도 있다.

function Button(){
	return (
		<button>클릭</button> // 버튼 컴포넌트
	)
}

export default function MyApp(){
	return (
            <div>
            <h1>Hello World!</h1>
            <Button /> // 버튼 컴포넌트를 가져와서 import
            </div>
    );
  }

가상 DOM

ReactDOM.render

ReactDOM.render를 사용하면 가상의 DOM을 출력할 수 있다. 하단의 방식은 react 17.0.2 버전을 cdn으로 리액트를 불러와서 사용한 것이다.

<!DOCTYPE html>
<html lang="kr">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>리액트 쓰기</title>
    <script src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
    <script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script>
      ReactDOM.render(
        React.createElement("h1", null, "hello!!"),
        document.getElementById("root")
      );
    </script>
  </body>
</html>

 

  • React.createElement(태그명, 속성값, 연결된 태그 안에 들어갈 내용)
  • 혹은 컴포넌트로 따로 만든 후에 그 컴포넌트를 createElement 해주어도 똑같이 출력된다.
<script>
      const root = document.getElementById("root");
      function MyComponent() {
        return React.createElement("div", null, "hello!");
      }
      ReactDOM.render(React.createElement(MyComponent), root);
    </script>

 

  • 또는 함수형 컴포넌트 형태로 불러오는 것이 가능하지만 이런 경우에는 바벨을 설치해야한다.
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type='text/babel'> // type에 'text/babel'을 추가해야 함!
...
ReactDOM.render(<MyComponent/>, root); // 바벨을 사용해야 사용이 가능하다.
</script>

 

Props

props는 리액트 컴포넌트에 전달할 수 있는 매개변수를 의미한다. props 전달은 바로 컴포넌트에 할당하여 객체 형태로 반환이 가능하다.

function MyComponent(props) {
        return React.createElement("div", null, `hello! ${props.name}`); // props 받기
      }
 ReactDOM.render(<MyComponent name="홍길동" />, root); // props 전달
// 위의 코드는 아래와 같이 작성할 수 있다.
// ReactDOM.render(React.createElement(MyComponent, {name: '홍길동'}) , root)

실습

리액트로 카운트 만들기

1. 리액트를 cdn으로 불러오기

function Counter() {
        const [count, setCount] = React.useState(0);

        const OnClickCountUp = () => {
          setCount(count + 1);
        };

        const OnClickCountDown = () => {
          setCount(count - 1);
        };

        return (
          <div>
            <h1>리액트로 카운터 만들기</h1>
            <button onClick={OnClickCountDown}>-</button>
            <span>{count}</span>
            <button onClick={OnClickCountUp}>+</button>
          </div>
        );
      }
      ReactDOM.render(<Counter />, root);
  • useState는 react에서 제공하는 메소드 중 하나로 상대값을 변경해주는 것이다.
  • 항상 기본 useState의 형식은 아래와 같다.
const [변수, 콜백함수] = useState(초기값)

// 변수를 사용하지 않을 경우에는
// const [_, 콜백함수] = useState(초기값)
// 으로 불러올 수 있다.

React로 click 버튼 카운트 만들기

  <body>
    <div id="root"></div>
    <script type="text/babel">
      let a = 0;
      const root = document.getElementById("root");
      //   const totalClicks = React.createElement(
      //     "h1",
      //     { style: { color: "blue" } },
      //     "Total clicks: 0"
      //   );
      //   const button = React.createElement(
      //     "button",
      //     {
      //       onClick: () => {
      //         console.log((a = a + 1));
      //       },
      //     },
      //     "Click me"
      //   );
      //   const container = React.createElement("div", null, [totalClicks, button]);

      const Title = (
        <h1 id="h1" style={{ color: "blue" }}>
          Total clicks: 0
        </h1>
      );

      const Button = (
        <button
          onClick={() => {
            a = a + 1;
            console.log(a);
          }}
        >
          Click me
        </button>
      );

      // 컴포넌트를 만들어서 여러 개를 재사용할 수 있다.
      const container = React.createElement("div", null, [
        Title,
        Button,
        Button,
      ]);

      // 함수형 컴포넌트로 만든 버전
      function Container() {
        const [click, setClick] = React.useState(0);

        const ClickUp = () => {
          setClick(click + 1);
        };

        return (
          <>
            <h1>Total clicks: {click}</h1>
            <button onClick={ClickUp}>Click me!</button>
          </>
        );
      }

      ReactDOM.render(<Container />, root);
    </script>
  </body>
</html>

 


"여기어때" 여행지 검색하는 폼 React CDN으로 만들기

<body>
    <div id="root"></div>
    <script type="text/babel">
      const Search = (
        <>
          <form
            method="get"
            action="https://www.goodchoice.kr/product/result"
            target="_self"
          >
            <input placeholder="장소입력" name="keyword" id="keyword" />
            <button type="submit">검색</button>
          </form>
        </>
      );

      const container = React.createElement("div", null, Search);
      ReactDOM.render(container, root);
    </script>
</body>
  • form에서 method 방식을 사용할 때에는 name을 입력하여 어떤 변수명으로 value를 받는지 명시해야한다.

로그인 만들기

 <script type="text/babel">
      const root = document.getElementById("root");

      function LoginForm() {
        const [ID, setID] = React.useState("");
        const [password, setPassword] = React.useState("");

        function handleID(e) {
          setUserInput(e.target.value);
        }

        function handlePassword(e) {
          setUserInput(e.target.value);
        }

        function handleSubmit(e) {
          e.preventDefault();
          console.log(ID, password);
        }

 	return (
          <form>
            <label for="ID">아이디:</label>
            <input id="ID" onChange={handleID} />
            <label for="password">비밀번호:</label>
            <input id="password" onChange={handlePassword} />
            <button onClick={handleSubmit} type="submit">
              로그인
            </button>
          </form>
        );
      }

      ReactDOM.render(<LoginForm />, root);
    </script>
  • useState를 이용하여 각 input에 입력되는 value 값을 onchange 메소드를 통해서 받았다.
  • 받은 값을 버튼을 클릭했을 때 console로 나오도록 onclick 메소드를 사용하면 끝.

 

      function LoginForm() {
        const [userInput, setUserInput] = React.useState({
          id: "",
          password: "",
        });

        function handleID(e) {
          setUserInput({ ...userInput, id: e.target.value });
        }

        function handlePassword(e) {
          setUserInput({ ...userInput, password: e.target.value });
        }

        function handleSubmit(e) {
          e.preventDefault();
          console.log(userInput.id, userInput.password);
        }
  • 크게 다른 점은 없어보이지만, 이렇게 같은 form 안에서 여러 정보를 받을 때에는 객체로 useState로 정보를 처리하는 것이 코드 유지보수 측면에서 도움이 된다고 생각하여 리팩토링 해보았다.
  • 단, 값을 전달할 때에는 객체를 구조분해하여 할당해야한다는 점을 꼭 잊지말아야한다! (예: {...userInput, id: e.target.value})

리액트로 이미지 랜덤변환하기

function Clock() {
        const [time, setTime] = React.useState(new Date());

        React.useEffect(() => {
          const id = setInterval(() => {
            setTime(new Date());
          }, 1000);
          return () => clearInterval(id);
        }, []);

        return (
          <div>
            <h1>Clock</h1>
            <span>{time.toLocaleTimeString()}</span>
          </div>
        );
      }

 ReactDOM.render(<Clock />, root);
  • clearInterval은 setInterval로 작업한 수행을 취소하는 역할을 한다.
  • toLocaleTimeString() 메서드는 생성된 Date 객체에서 시간 부분을 현재 지역 표기법으로 변환한다.

 

본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.

더보기

출처 및 참고자료

1. fetch 사용시 유의사항(json() 함수 사용하기), https://wooooooak.github.io/javascript/2018/11/25/fetch&json()/ 

 

fetch사용시 유의 사항 (json() 함수 사용하기) · 쾌락코딩

fetch사용시 유의 사항 (json() 함수 사용하기) 25 Nov 2018 | javascript network basic js 개발자들은 network request 요청이 필요할 경우 대부분 axios.js나 기타 다른 라이브러리를 쓰는 것 같다. js에서 기본적으

wooooooak.github.io

2. fetch()의 response.json()는 왜 Promise를 리턴하는 걸까?, https://moneytech.kr/42

 

fetch()의 response.json()은 왜 Promise를 리턴하는 걸까?

세줄 요약 : 1. 우리가 받은 Response 객체는 완전한게 아니다. 아직 데이터를 받는 중인 것이기에 Promise를 반환 2. 그래서 다 받고 난 뒤에 온전한 Response 객체상태에서 작업을 하는 것이다. 3. 그게

moneytech.kr

3. Promise.prototype.then(), https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/then

 

Promise.prototype.then() - JavaScript | MDN

then() 메서드는 Promise (en-US)를 리턴하고 두 개의 콜백 함수를 인수로 받습니다. 하나는 Promise가 이행했을 때, 다른 하나는 거부했을 때를 위한 콜백 함수입니다.

developer.mozilla.org

4. Github에 API 키 숨기기, https://velog.io/@kimjumpsun_code/Github%EC%97%90-API-Key-%EC%88%A8%EA%B8%B0%EA%B8%B0

 

Github에 API Key 숨기기

이번 5월 노마드 챌린지를 하기 이전에 나는 이미 api key를 그대로 push 했었다. 그나마 다행인건 private으로 올렸다는 것인데.. 이번 기회에 환경변수를 사용하지 않는 간단한 방법을 알게되어 기

velog.io

5. useState object 형태로 업데이트하기, https://jaddong.tistory.com/entry/useState-object-%ED%98%95%ED%83%9C-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8%ED%95%98%EA%B8%B0