[React&TS] 1. Todo 앱 작성, openweathermap 통신

파이·2021년 12월 9일
1

저번 글에 이어 Todo를 작성하는 부분입니다.

2. Todo.tsx 작성

Todo 기본세팅

작성하기전에 필요한 부분을 import합니다.

...
import { RootState } from "../redux/redux-index";
import { deleteTodo, checkTodo } from "../redux/modules/todo-reducer";

다른 코드들도 필요하지만 특히 주의할 게 있다면,
저번 글에 마지막에 작성한 RootState와
미리 reducer 부분에서 지정해 놓은 todo 함수들을 가져와서 간편하게 써보도록 합시다.

const Todo = () => {

  const todoReducer = useSelector( (state: RootState) => state.todoReducer)
  const dispatch = useDispatch()
  ...

RootState는 여기서 쓰입니다. useSelect로 가져올 state의 타입을 지정해주기 위한 type 입니다.



Todo 나머지 작성

 const checkStyle = {
    textDecoration: "line-through",
    color: "#ccc"
  }

  const todoList = todoReducer.map( v =>
    <div className="todo-memo">
      <li className= 'content' style= { v.checked ? checkStyle : undefined}
          onClick= { () => dispatch(checkTodo(v.id)) }
        > {v.content} 
      </li>
      <span className= 'del-btn' onClick= { () => dispatch(deleteTodo(v.id)) }> <i className="far fa-trash-alt"></i> </span>
    </div>
    )

사실 나머지는 거의 비슷합니다.
완료한 todo를 작성하는 checkTodo 함수를 dispatch할때만 조금 신경써주면 될 것 같습니다.



3. TodoInput.tsx 작성

input은 더더욱 별거 없습니다. 딱 한가지만 유의하면 되는데,

 const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  	...
  }

딱 이렇게 event 인자를 받아오는 부분이 굉장히 길고 복잡합니다.
나눠서 보면, React.이벤트명<HTML타입>
이렇게 작성해주면 되긴 하지만... 모든 타입을 저렇게 유추하기란 쉽지 않습니다.
때문에,

우리가 이벤트 인자를 넣은 부분에 마우스 커서를 올려주면 어떤 유형인지 타입을 그대로 보여줍니다. 와우!
그대로 복사해서 붙여넣어주시면 됩니다ㅎㅎ.

이렇게 나머지 코드를 작성하면 Todo는 끝입니다. 참 쉽죠?




...

이제 조금 Todo를 디벨롭 시켜보겠습니다.

4. REST API를 활용한 통신

사실 TS를 사용하면서 REST API 통신 부분에서 크게 다르지 않습니다.
그러나 API 통신이 익숙하지 않은 만큼 참고 삼아 적어놓도록 하겠습니다.

참고로, 전 Axios 를 이용하였습니다.

weather.tsx

인터페이스 지정하기

export interface IWeatherData {
  weather: IWeather, tamp: ITamp
}
export interface IWeather {
  main: string, description: string, icon: string
}
export interface ITamp {
  now: number, humidity: number
}

우선 필요한 데이터의 interface부터 작성했습니다.
중첩된 object인 만큼, 자식 interface를 지정하여 부모 interface에 넣어줬습니다. 깔끔!



좌표값 받아오기

const Weather = () => {
  const [API_URL, setAPI_URL] = useState("")
  const [weatherData, setWeatherData] = useState<IWeatherData | null>(null) // 1

  // 위치 요청함수
  const requestCoords = () => {
    navigator.geolocation.getCurrentPosition(handleGeoSucc, handleGeoErr); // 2
  }
  // 요청 성공시
  const handleGeoSucc = (position: any) => { // 3
    const lat = position.coords.latitude;  // 경도  
    const lon = position.coords.longitude;  // 위도
    setAPI_URL(`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=74dbeb38347e356be38594a5938cf3ea&units=metric`)
  }
  // 요청 실패시
  const handleGeoErr = (err: any) => { // 4
    console.log("geo err! " + err);
  }
  
  ...

굉장히 길지만 하나씩 뜯어보면 별거 없습니다. 대부분 openweathermap의 문법 그대로 작성한것이니, 웬만하면 따라하시길 추천드립니다. 저도 다른 글을 참고하여 따라한 것 입니다. ㅎㅎ

  1. useState의 타입을 지정해줍니다. 기초값이 null이고, 통신이 끝난 후에 IWeatherData 형식의 값으로 바뀌기 때문에 Generic 문법을 활용하여 useState의 타입을 지정했습니다.

  2. 좌표를 요청하는 함수입니다. getCurrentPosition가 인자 두개를 받는데, 아래 작성하는 성공, 실패 함수입니다.

  3. 성공시 경도와 위도를 받아와주고, useState인 API_URL에 템플릿 리터럴 형태로 set 해줍니다. 이후 이API_URL을 통해 날씨 값을 받아와 줄 것 입니다.

  4. 요청 실패시, 콘솔로 에러를 찍어줍니다.

REST API의 형식의 좀 겁먹었던 것 같은데, 한번 해보니 너무 이해가 잘 되더라구요.



axios를 이용해 데이터 가져오기

  // Axios로 openweathermap를 요청하는 함수
  const getData = async () => {
    const config = {  // 1
    headers: {
      'Accept': 'application/json'
    }}
    
    try {
      const res = await axios.get(API_URL, config) // 2
      console.log(res.data);
        let weatherObj = { // 3
          weather: {
            main: res.data.weather[0].main,
            description: res.data.weather[0].description,
            icon: res.data.weather[0].icon
          },
          tamp: { 
            now: res.data.main.temp,
            humidity: res.data.main.humidity
          }
        }
        setWeatherData(weatherObj); // 4
    } catch (err) { // 5
      console.log('에러:', err)
    }
   }

   useEffect(() => {
    requestCoords() // 시작할때 위치데이터를 요청하고 // 6
    if (API_URL !== '') { // URL 위치를 받아온 이후 데이터 요청 실행 // 7
      getData();
    }
  }, [API_URL])
  

복잡하니 하나씩 뜯어봅시다.

  1. config 구성요소입니다. header를 제외하고도 굉장히 많은 설정 값들이 들어갈 수 있습니다. 사실 작성하지 않아도 무방하지만, Localhost를 제외한 배포환경에서 오류를 방지하기 위해 찾아서 넣어준 방법입니다. 그 외에 설정은 필요할 때 찾아봅시다!

  2. await 문법으로 axios의 값을 받아옵니다. 데이터를 사용하는 방식을 지정하는 것이 좋으므로, .get 으로 데이터를 받아왔습니다. json을 한번 더 받아오는 단계가 없는게 편합니다.

  3. 받아온 데이터를 가공해줍니다. 아까 IWeatherData로 받아온 형식에 맞게 데이터를 뽑아와 줍니다. 문뜩 이 부분을 보고 있자니, GraphQL도 배워보고 싶습니다.

  4. 가공한 데이터를 set해줍니다.

  5. 에러가 있다면 에러를 던져줍니다. try 형식으로 데이터를 받아왔기에, catch (err) 로 사용했습니다.

  6. 처음 Weather 컴포넌트를 render시, 좌표값을 받아줍니다.

  7. 좌표값이 API_URL로 설정되어 useEffect가 재실행됩니다. 이 때는 API_URL이 빈 string값이 아니므로, 날씨 정보를 받아온 getData() 함수가 정상적으로 실행됩니다.


리턴값 작성

      <div className="weather-ui">
        { weatherData 
          ? <>
              <Temperature weatherData= {weatherData} />
              <Conditions weatherData= {weatherData} />
              <Humidity weatherData= {weatherData} />
            </>
          : <h1> 위치 정보를 받아올 수 없습니다. </h1>
        }
      </div>

weatherData가 초기값인 null 이 아닐때, 받아온 값을 표시해주는 자식 컴포넌트들을 띄워줍니다. null 일때는 오류메세지로 대신합시다.



5. 온도, 습도, 날씨 컴포넌트 작성하기

이제 자식 컴포넌트들만 작성해주면 끝입니다.

Temparature.tsx

import React from "react";
import { IWeatherData } from "./Weather";

export interface IWDate { 
 weatherData: IWeatherData
}

const Temperature = ( { weatherData }: IWDate ) => {

 const tempUi = () => { 
     let value =
       <div className= 'temp-ui'>
         <h2> Temparture </h2>
         <i className="fas fa-temperature-low"></i>
         <div> {weatherData.tamp.now.toFixed(1)}℃</div>
       </div>
   return value
 }
 
 return (
   <>
     { tempUi() }
   </>
 )
}

export default Temperature;

전체 코드입니다.
사실 어려운 부분은 없지만, props를 받아오는 부분이 조금 독특합니다.
기존 props를 구조분해 해서 쓰는걸 type과 함께 쓰는거라고 보면 될 것 같은데요,
받아온 props를 빠짐없이 interface로 지정해서 { weatherData }: IWDate 이러한 형식으로 사용해주면 될 것 같습니다.


나머지 습도와 날씨 부분도 똑같습니다. 다만 날씨부분은 기존 openweathermap의 날씨 아이콘이 못생겨서 FontAwesome 이용해서 새로 바꿔줬습니다.

conditions.tsx

저는 이런식으로 지정해줬습니다.
다만, icons의 타입을 도저히 모르겠어서 any타입으로 사용해준 부분이 아쉽습니다.


도저히 알 수 없었던 icons의 타입을 제외하곤 REST API라고 TS 환경에서 크게 다르지 않습니다.
그래도 axios랑 좀 더 친해졌던 계기같아서 만족스러운 부분이었습니다.

다음은 굳이 Todo를 Redux로 작성한 부분을 활용해보는 Calendar 파트로 오겠습니다.

profile
기록

0개의 댓글