React 재활훈련- 9일차, Module CSS, API호출, List key

0

react

목록 보기
9/11

https://www.udemy.com/course/react-next-master/

module css

react css파일들의 요소들은 사실 global하게 적용된다. 즉, A react component에 적용한 header tag style이 다른 react component에 적용된다는 것이다. 이를 해결하기 위해서 css파일을 하나의 모듈처럼 사용할 수 있는 방법이 있다.

먼저 css파일의 이름을 {name}.module.css형식으로 만들도록 하자.

  • Layout.module.css
.header {
    position: fixed;
    top: 0px;
    left: 0px;
    right: 0px;
    height: 50px;
    background-color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
}

.main {
    max-width: 700px;
    margin: 0 auto;
    padding: 80px 10px;
}

다음의 css 모듈을 만들었다면 이를 import하되 모듈을 불러오듯이 할 수 있다.

  • Layout.jsx
import style from "./Layout.module.css"

export default function Layout({children}) {
    return (
        <div>
            <header className={style.header}>
                <div>NARAS</div>
            </header>
            <main className={style.main}>
                {children}
            </main>
        </div>
    )
}

style css module을 가져와 style.header, style.main으로 우리가 정의한 css 정의들을 불러올 수 있다. 이렇게하면 css파일이 import되어 global하게 적용되어도 중복되거나, 다른 react componnet에 style이 적용되지 않는다.

왜 그럴까?? 는 개발자 도구를 통해 브라우저에 렌더링된 결과를 보면 알 수 있다.

<header class="_header_g67g0_1"><div>NARAS</div></header>

header의 class가 임의의 이름으로 들어간 것을 알 수 있다. 이는 css를 모듈처럼 사용하여 배치하였기 때문에 브라우저에서 임의의 값으로 정의하여 적용한 것이다. 따라서, class이름이 서로 중복될 일이 없고 code를 좀 더 단순하게 관리할 수 있으며, global하게 css들이 적용, 중복되는 일이 없어진다.

API 호출하기

먼저 편리하게 HTTP request를 보낼 수 있는 axios library를 설치하도록 하자.

npm i axios

axios의 사용방법은 매우 단순하다. 단, axios와 같은 http request 호출 code들은 모두 비동기 동작이기 때문에 async-await으로 이 동작을 처리해야한다.

import axios from "axios"
import { useEffect, useState } from "react"

async function fetchCountries () {
    try {
        const res = await axios.get("https://naras-api.vercel.app/all")
        return res.data
    } catch(e) {
        return []
    }
}

export default function Home() {
    const [countries, setCountries] = useState([])
    useEffect( async () => {
        const data = await fetchCountries()
        setCountries(data)
    },[])
    return <div>Home</div>
}

fetchCountriesasync-await함수로 처리한 것을 볼 수 있다. 또한, fetchCountries함수를 Home component에 만들지 않은 이유는, component 내부 함수는 리렌더링 시에 다시 정의되므로 불필요한 낭비가 없도록 위에 넣은 것이다.

참고로 useEffect의 callback함수도 fetchCountries를 호출하기 위해서 async-await으로 설정되어야 한다.

하지만, useEffect안에 async-await을 넣는 것은 좋지 못하다. 왜냐하면 다음의 이슈가 있기 때문이다.
https://stackoverflow.com/questions/74265321/uncaught-typeerror-destroy-is-not-a-function-error-in-react

따라서, useEffect안의 async 함수는 다른 함수로 빼내어 사용하는 것이 좋다.

export default function Home() {
    const [countries, setCountries] = useState([])

    const setInitData = async () => {
        const data = await fetchCountries()
        setCountries(data)
    }
    
    useEffect(() => {
        setInitData()
    },[]
    ...
}

api를 호출할 때, 비동기적으로 호출한다고 하였다. 비동기 호출을 조심해야하는데, 다음의 code를 보도록 하자.

...
export default function Country() {
    const params = useParams()
    const [country, setCountry] = useState()

    const setInitData = async () => {
        const data = await fetchCountry(params.code)
        setCountry(data)
    }

    useEffect(() => {
        setInitData()
    }, [params.code])

    return (
        <div className={style.container}>
            <div className={style.header}>
                <div className={style.commonName}>
                    {country.flagEmoji}&nbsp;{country.commonName}
                </div>
            </div>
        </div>
    )
}

겉보기에는 문제없이 구동될 것 같아보이지만, 실제로는 error가 발생하여 렌더링에 실패한다. 왜일까?? 이는 country가 undefined이기 때문이다. 왜 country가 undefined이냐면, 처음 렌더링될 때 country state를 업데이트하는 setInitData가 비동기이기 때문에 아직 country 객체가 채워지지 않은 상태에서 렌더링이 되기 때문이다.

따라서, 비동기 api를 호출할 때는 데이터가 담길 때까지 로딩창을 보여주는 것이 좋다.

...
export default function Country() {
    const params = useParams()
    const [country, setCountry] = useState()

    const setInitData = async () => {
        const data = await fetchCountry(params.code)
        setCountry(data)
    }

    useEffect(() => {
        setInitData()
    }, [params.code])

    if(!country) {
        return <div>Loading ...</div>
    }

    return (
        <div className={style.container}>
            <div className={style.header}>
                <div className={style.commonName}>
                    {country.flagEmoji}&nbsp;{country.commonName}
                </div>
            </div>
        </div>
    )
}

이렇게 if문을 통해서 countryundefined이라면 Loading 글자를 보여주도록 하는 것이다. 비동기적으로 호출되는 setInitData는 시간이 지나서 country state를 update하고 리렌더링이 되어 우리가 원하는 component를 반환하게 된다.

Default props와 List 렌더링

다음은 CountryList라는 component로 CountryItem을 N개 가지고 있는 component로 보면된다.

import CountryItem from "./CountryItem"

export default function CountryList({countries}) {
    return (
        <div>
            {countries.map((country) => {
                return <CountryItem key={country.code} {...country} />
            })}
        </div>
    )
}

CountryList.defaultProps = {
    countries: [],
}

나라에 대한 정보가 담긴 countries 배열을 받아와 CountryItem component를 렌더링하는 것인데, react에서 동일한 여러 개의 component를 렌더링할 때는 반드시 key를 설정해주어야 한다. 그래야 서로 간의 component가 식별이 되기 때문이다. 만약, 삭제나 update와 같은 연산을 할 때 key가 설정되어 있지 않으면 제대로 실행되지 않는 것을 볼 수 있다.

또한, countries는 배열이 와야하는데, 배열이 아닌 객체가 오면 map method가 실행되지 않는다. 따라서 default props로 countries의 값을 배열로 선언해줄 수 있는데, 이것이 defaultProps이다. 위에서 CountryList.defaultProps처럼 기본 props를 설정해주어 원치않은 오류가 발생하지 않도록 막을 수 있다.

0개의 댓글

Powered by GraphCDN, the GraphQL CDN