https://www.udemy.com/course/react-next-master/
react css파일들의 요소들은 사실 global하게 적용된다. 즉, A react component에 적용한 header
tag style이 다른 react component에 적용된다는 것이다. 이를 해결하기 위해서 css파일을 하나의 모듈처럼 사용할 수 있는 방법이 있다.
먼저 css
파일의 이름을 {name}.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하되 모듈을 불러오듯이 할 수 있다.
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들이 적용, 중복되는 일이 없어진다.
먼저 편리하게 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>
}
fetchCountries
를 async-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} {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} {country.commonName}
</div>
</div>
</div>
)
}
이렇게 if
문을 통해서 country
가 undefined
이라면 Loading
글자를 보여주도록 하는 것이다. 비동기적으로 호출되는 setInitData
는 시간이 지나서 country
state를 update하고 리렌더링이 되어 우리가 원하는 component를 반환하게 된다.
다음은 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를 설정해주어 원치않은 오류가 발생하지 않도록 막을 수 있다.