[URECA] Day54 리액트 lazy, suspense, axios
💡 Lighthhouse
F12 → 개발자 도구 → Lighthouse
Lighthouse는 무엇인가? 🤔
- 웹 성능 최적화 도구
- 총 4가지( performace, accessibilty, best practices, seo )로 검사
주의사항
- 테스트 환경에 따라 점수가 달라짐
- 네트워크 상태, 시스템 성능 등 환경 요소가 결과에 여향
- 비즈니스 우선순위와 균형 유지
- 사용자 경험 개선이 목표
💡성능 향상을 위해 검토할 내용 → 자바스크립트 최적화 → 라우터 구성에서 lazy 컴포넌트를 Suspense로 감싸기
🔍 lazy()에 대해서 알아보자!
const MainPage = lazy(() => import('./pages/MainPage'))
const AboutPage = lazy(() => import('./pages/AboutPage'))
import구문 말고 변수로 선언해서 lazy하는 이유는 뭐야?
정적vs동적 임포트 차이때문이다.
A. import구문은 정적이다.
import AboutPage from './pages/AboutPage'
- 앱이 시작될때 무조건 로딩된다.
- 컴파일(빌드) 시점에 딱 고정되어 있다.
- 코드 스플리팅이 안된다.
B. lazy(()=>import(...))는 동적 import
const AboutPage = lazy(() => import('./pages/AboutPage'))
- 실행 중 필요한 순간에만 로딩된다.
- 웹팩이 이걸 보고 chunk로 분리해서 따로 로딩한다.
- 코드 스프리팅이 가능해진다.
이유 | 설명 |
import | 처음부터 모든 짐을 한 번에 가방에 넣고 출발 (무겁고 느림) |
lazy(() => import()) | 필요한 짐만 그때그때 배달받음 (가볍고 빠름) |
🤔 그렇다면 왜 변수로 선언해야할까❓
lazy()는 React.Component타입을 반환한다. 그래서 JSX에서 <AboutPage />처럼 사용하려면 변수에 담아야한다.
만일 그냥 import만 쓰면 함수 반환값이라 JSX에서 사용하지 못한다.
🤔 코드 스플리팅이란 무엇일까❓
React.lazy()는 필요한 순산에만 컴포넌트를 불러오는 방식이다. 이를 지연로딩(Lazy Loading) 또는 코드 스플리팅이라고 한다.
✅ 요약
이유 | 설명 |
import | 처음부터 모든 짐을 한 번에 가방에 넣고 출발 (무겁고 느림) |
lazy(() => import()) | 필요한 짐만 그때그때 배달받음 (가볍고 빠름) |
🔍 Suspense에 대해 알아보자
import React from 'react'
...
import { Outlet } from 'react-router-dom'
import { Suspense } from 'react'
const Default = () => {
return (
<>
...
<Suspense fallback={<div>Loading...</div>}>
<Outlet />
</Suspense>
...
</>
)
}
export default Default
🤔 왜 <Outlet />을 <Suspense>로 감싸❓
<Outlet />은 react-router-dom에서 중첩 라우팅을 구현할때 자식 컴포넌트가 표시되는 자리이다.
Outlet에 대한 설명 ⬇️
근데 자식 컴포넌트들은 React.lazy()로 동적 import 한다.
const MainPage = lazy(() => import('./pages/MainPage'))
이렇게 lazy로 불러온 컴포넌트는 비동기 로딩이라, 렌더링 전에 준비가 안되어있을 수 있다.
비동기 대한 설명⬇️
비동기
- 답변을 기다리지 않는 것 👉🏻 바로 일 수행
- 자원을 효율적으로 사용 그러나 업무가 복잡해
- 결과를 기다리는 않는것
https://recordoftheday.tistory.com/entry/JS-동기-비동기
[JavaScript] 동기 비동기
동기 답변을 기다리는 것 👉🏻 그 뒤 일 수행 단순하나 자원을 비효율적으로 사용결과를 기다리는 것동기식에서 답변을 기다리는 상태를 Blocking 이라고함 비동기 답변을 기다리지 않는 것 👉
recordoftheday.tistory.com
그런 경우에는 React는 컴포넌트로 바로 렌더 못하고 대기 상태에 빠지게 된다.
대기 상태에 대한 설명 ⬇️
리액트가 어떤 컴포넌트를 렌더링하려고 하는데, 그 컴포넌트가 아직 준비되지 않아서 기다리는 상태
const AboutPage = lazy(() => import('./pages/AboutPage'));
lazy로 불러온 컴포넌트는 실제로 비동기로 불러온다. 그러면 React는 AboutPage를 렌더링하기 전에 기다려야 한다.
그런데 만일..! Suspense가 없으면?
React는 컴포넌트를 기다릴 준비가 안 되어 있어서 에러 발생
React.lazy()는 내부적으로 "Promise"를 던짐 (throw Promise) → React는 깜짝 놀란다😲
그래서 Suspense가 필요한 것이다.
🗣️"야! 얘 로딩 안됐을 수도 있으니까 기다리는 동안 fallback 보여줄게"
🔄 흐름 상태
1. lazy 컴포넌트가 로딩되면
2. Promise를 던지고 → React가 "대기 상태"로 진입
3. 이때 Suspense가 감싸고 있으면
4. fallback UI를 보여주고 → 컴포넌트가 다 불러와지면
5. 진짜 컴포넌트를 다시 렌더링
그래서
Suspense
는 lazy 컴포넌트를 기다리는 동안 보여줄 UI를 지정하는 역할을 한다.
<Suspense fallback={<div>Loading...</div>}>
<Outlet />
</Suspense>
👉🏻 Outlet안에 있는 컴포넌트가 lazy하게 로딩되는 동안, <div> Loading... </div>이걸 보여준다 는 의미
fallback에 대한 설명 ⬇️
fallback은 lazy 컴포넌트가 로딩될 때 보여줄 임시 컴포넌트
예: 로딩 스피너, 메시지, 스켈레톤 UI 등
<Suspense fallback={<MySkeleton />}> <Outlet /> </Suspense>
💡React에서 API 데이터를 가져오는 방법
🤔 Axios를 설치하는 이유는 뭘까?
npm i axios
- axios는 HTTP 요청을 간편하게 보낼 수 있게 도와주는 라이브러리이다.
- fetch보다 코드가 간결하고, interceptors 같은 기능도 있어 확장성이 좋다.
- get, post, put, delete 같은 REST API 요청을 쉽게 다를 수 있다.
🤔 api/bannerApi.js를 분리하는 이유는 뭘까?
- API 호출 로직을 한 곳에 모아두면 재사용하기 쉽고 유지보수가 편해진다.
📃 주요 코드 설명 - bannerApi.js
import axios from 'axios'
const BASE_URL = 'http://localhost:3000/banners/'
export const getBannerData = async () => {
try {
const res = await axios.get(`${BASE_URL}`)
// console.log('res----', res)
return res.data
} catch (err) {
console.log('err----', err)
// throw err
}
}
하나하나 코드를 이해봅시당!
import axios from 'axios'
- axios라는 외부 라이브러리를 가져온다.
- 서버와 통신을 하려면 fetch같은걸 사용해야하는데, axios는 이걸 더 쉽게 해주는 도구이다.
👉🏻 axios를 파일에서 사용할 수 있게 준비하자! 라는 의미이다.
const BASE_URL = 'http://localhost:3000/banners/'
- 서버에 있는 데이터를 가져올 주소(URL)를 변수로 저장한 것
- 이 주소는 JSON Server 경로이다.
try { ... } catch (err) { ... }
- 에러가 나도 앱이 멈추지 않게 하기 위한 보호막
- try안에 있는 코드에서 문제가 생기면 catch에서 대신 처리해준다.
const res = await axios.get(${BASE_URL})
- 서버에 데이터를 요청하는 코드
- axios.get(...)은 해당 주소에서 데이터를 가져오는 명령이고,
- await는 "서버가 응답할 때까지 기다려줘"라는 의미
- res는 서버의 응답 전체를 담은 객체이다.
return res.data
- res.data는 서버가 보내준 실제 데이터 부분만 추출한 것이다.
개발자 도구에는 정말 무궁무진한 기능들이 숨어 있다는 걸 새삼 느꼈다.
성능 최적화 도구까지 포함되어 있다니, 놀라울 따름이다!
또한 이번 작업을 통해, 컴포넌트는 렌더링만 담당하고, API 호출이나 비즈니스 로직은 별도로 분리해야 유지보수성이 높아진다는 걸 깨달았다.
앞으로는 API 관련 코드는 무조건 api 디렉토리에 정리하는 습관을 들이기로 다짐했다.
이런 웹사이트를 구현하는 개발자들은 얼마나 밤을 새워가며 고생했을까 생각이 들었다.
물론 워라벨이 중요한 삶이면 그 반대일 수도?
내가 해야할 개발공부는 스택처럼 쌓여간다.