라이프 사이클 (생명주기) 이해하기
React에서 컴포넌트의 생명주기를 관리하는 것은 앱의 성능과 유지보수성에 중요한 영향을 미칩니다.
특히, 함수형 컴포넌트에서는 useEffect
훅을 사용하여 이러한 관리 작업을 수행합니다.
- 뉴스 예제로 알아보는 라이프 사이클
데이터를 불러와 화면에 표시하는 간단한 뉴스 목록 컴포넌트를 생각해볼 수 있습니다.
생명주기 단계 | 소셜 미디어 대시보드 |
---|---|
Mounting (마운팅) | 데이터 가져오기 |
Updating (업데이팅) | 실시간 업데이트 |
Unmounting (언마운팅) | 컴포넌트가 DOM에서 제거되는 단계 |
마운트 (Mounting) - 태어나고
- 이 컴포넌트는 useEffect 훅을 사용해 컴포넌트가 마운트될 때 뉴스 API에서 데이터를 한 번만 불러오고,
- 이 데이터를 컴포넌트의 상태로 저장하여 화면에 뉴스 목록을 렌더링합니다.
업데이트 (Updating) - 성장하고
- 사용자가 특정 카테고리를 선택하면,
- 해당 카테고리에 맞는 뉴스 데이터를 다시 불러와 업데이트할 수 있습니다.
언마운트 (Unmounting) - 죽는다
- 컴포넌트가 화면에서 사라지면, 뉴스 데이터를 불러오는 작업을 중지하고,
- 불필요한 리소스를 해제하여 메모리 누수를 방지합니다.
이렇게 useEffect
를 사용하면 API 호출 타이밍을 정확히 조절하여 필요할 때만 데이터를 불러올 수 있으므로 앱의 성능을 향상시키고 불필요한 데이터 요청을 줄일 수 있습니다.
리액트 컴포넌트의 생명주기에 따라 특정한 작업을 수행하고 싶을 때, useEffect
훅을 사용할 수 있습니다.
- 페이지가 마운팅, 업데이트, 언마운팅될 때 body의 배경색을 변경하는 코드
import React, { useEffect, useState } from 'react'
function App() {
const [color, setColor] = useState('blue')
// 마운트시에만 실행
useEffect(() => {
console.log('마운트시에만 실행')
// body에 배경색 추가
document.body.style.backgroundColor = 'blue'
// 언마운트시에만 실행
return () => {
// body에 배경색 제거
document.body.style.backgroundColor = ''
}
}, [])
// color 상태 업데이트시에만 실행
useEffect(() => {
console.log('업데이트시에만 실행')
// body에 배경색 추가
document.body.style.backgroundColor = color
}, [color])
return (
<div>
<div>안녕하세요</div>
<button onClick={'yellow'}>색상 변경</button>
</div>
)
}
export default App
useEffect 로 라이프 사이클 제어하기
useEffect
훅을 사용하면 컴포넌트의 생명주기에 따라 특정한 작업을 수행할 수 있습니다.useEffect
훅을 사용하여 컴포넌트가 마운트될 때, 업데이트될 때, 언마운트될 때 특정한 작업을 수행할 수 있습니다.
생명주기 단계 | 소셜 미디어 대시보드 |
---|---|
Mounting (마운팅) | 데이터 가져오기 |
Updating (업데이팅) | 실시간 업데이트 |
Unmounting (언마운팅) | 컴포넌트가 DOM에서 제거되는 단계 |
- 마운트 제어하기
useEffect(() => {...}, [])
의존성 배열을 빈 배열로 전달하면, useEffect
의 콜백함수는 컴포넌트가 마운트될 때만 실행됩니다.
ex) 뉴스 데이터를 불러오는 작업을 컴포넌트가 마운트될 때만 실행
import React, { useState, useEffect } from 'react'
export default function App() {
const [count, setCount] = useState(0)
// useEffect(() => {...}, []) 의존성 배열로 빈 배열을 전달하면
// 컴포넌트가 마운트될 때만 useEffect의 콜백함수가 실행됩니다.
useEffect(() => {
console.log('마운트될 때만 실행')
}, [])
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
- 마운트 & 업데이트 제어하기
useEffect(() => {...})
의존성 배열을 전달하지 않으면, useEffect
의 콜백 함수는 컴포넌트가 마운트될 때와 업데이트될 때 모두 실행됩니다.
import React, { useState, useEffect } from 'react'
export default function App() {
const [count, setCount] = useState(0)
// useEffect(() => {...}) 의존성 배열을 전달하지 않았으므로,
// 콜백 함수는 마운트 시점에도 실행되고,
// 업데이트 시점에도 실행됩니다.
useEffect(() => {
console.log('마운트, 업데이트시 실행')
})
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
- 업데이트 제어하기
useEffect(() => {...}, [count])
의존성 배열에 특정한 상태를 전달하면, 해당 상태가 변경될 때만 useEffect
의 콜백 함수가 실행됩니다.
ex) 카테고리가 변경될 때만 뉴스 데이터를 불러오는 경우
import React, { useRef, useState, useEffect } from 'react'
export default function App() {
const [count, setCount] = useState(0)
// useRef 훅을 사용하여 컴포넌트의 변수 didMountRef를 생성
// 현재 컴포넌트가 마운트했는지 판단하는 변수 didMountRef를 Ref 객체로 생성
// 초기값은 false
const didMountRef = useRef(false)
// 의존성 배열로 아무것도 전달하지 않았으므로, 콜백 함수는 마운트 시점에도 실행되고, 업데이트 시점에도 실행됩니다.
useEffect(() => {
// !didMountRef.current는 didMountRef.current가 false일 때 true를 반환하고, true일 때 false를 반환합니다.
// 마운트 시점에는 조건(!false=true)은 참이므로, if문 안의 코드가 실행되고 didMountRef.current를 true로 설정합니다.
// 이후에 컴포넌트가 업데이트될 때는 조건(!true=false)은 거짓이므로, console.log('업데이트될 때만 실행')이 실행됩니다.
if (!didMountRef.current) {
// didMountRef.current를 true로 설정 후 함수를 종료
didMountRef.current = true
return
} else {
console.log('업데이트될 때만 실행')
}
})
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
컴포넌트 언마운트와 클린업 함수
useEffect(return () => {...})
컴포넌트가 언마운트되면, React는 해당 컴포넌트와 관련된 모든 리소스를 해제하려고 시도합니다. 이 과정에서 useEffect 내에서 반환하는 클린업 함수가 중요한 역할을 합니다.
- 클린업 함수의 역할
useEffect 내에서 반환하는 클린업 함수는 컴포넌트가 언마운트될 때 실행됩니다. 이 함수는 메모리 누수를 방지하고, 필요하지 않은 작업이 백그라운드에서 계속 실행되는 것을 막기 위해 사용됩니다. 특히, setInterval과 같은 타이머를 사용할 때 이 클린업 과정이 중요합니다.
컴포넌트의 "언마운트"는 컴포넌트가 더 이상 화면에 표시되지 않을 때 발생합니다.
- 클린업 함수 예제
import { useEffect, useState } from 'react'
const Timer = () => {
const [timer, setTimer] = useState(0)
const [isShow, setIsShow] = useState(true)
useEffect(() => {
let interval
if (isShow) {
interval = setInterval(() => {
setTimer((prev) => prev + 1)
}, 1000)
}
// 타이머를 시작하는 interval을 설정합니다.
// 이 리턴 함수는 컴포넌트가 언마운트 될 때 호출됩니다.
// 컴포넌트가 제거되면 interval도 정리해야 하기 때문입니다.
return () => {
console.log('타이머 정리')
clearInterval(interval)
}
}, [isShow]) // 종속성 배열이 비어 있으므로 마운트 시에만 실행
const hideTimer = () => {
setIsShow(false)
setTimer(0) // 타이머 리셋
}
return (
<div>
{isShow ? (
<div>
<h2>{timer}초</h2>
<button onClick={hideTimer}>타이머 숨기기</button>
</div>
) : (
<button onClick={() => setIsShow(true)}>타이머 보이기</button>
)}
</div>
)
}
export default Timer
localStorage를 사용한 counter 데이터 저장
localStorage
는 브라우저에 데이터를 저장하는 방법 중 하나로, 쿠키와 비슷한 역할을 합니다.localStorage
에 저장된 데이터는 브라우저를 닫아도 유지되며, 브라우저를 다시 열 때에도 데이터가 유지됩니다.localStorage
에 저장된 데이터는 문자열 형태로 저장되므로, 객체를 저장할 때는JSON.stringify
로 문자열로 변환하여 저장하고, 불러올 때는JSON.parse
로 객체로 변환하여 사용합니다.
- localStorage 사용 예제
import React, { useEffect, useState } from 'react'
const Counter = () => {
const [count, setCount] = useState(0)
// localStorage에 저장된 count 값을 불러와 초기값으로 설정
useEffect(() => {
const savedCount = localStorage.getItem('count')
if (savedCount) {
setCount(Number(savedCount))
}
}, [])
// count 값이 변경될 때마다 localStorage에 저장
useEffect(() => {
localStorage.setItem('count', count)
}, [count])
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
</div>
)
}
export default Counter
useEffect 뉴스 카테고리 선택 기능 구현하기
- 이 컴포넌트는 useEffect 훅을 사용해 컴포넌트가 마운트될 때 뉴스 API에서 데이터를 한 번만 불러오고,
- 이 데이터를 컴포넌트의 상태로 저장하여 화면에 뉴스 목록을 렌더링합니다.
- 만약 사용자가 특정 카테고리를 선택하면,
- 해당 카테고리에 맞는 뉴스 데이터를 다시 불러와 업데이트할 수 있습니다.
"use client";
import axios from "axios";
import Image from "next/image";
import Link from "next/link";
import React, { useEffect, useState } from "react";
const NewsPage = () => {
const [news, setNews] = useState([]);
const [categories, setCategories] = useState([]);
const [selectedCategory, setSelectedCategory] = useState("");
useEffect(() => {
const fetchNews = async () => {
try {
const response = await axios.get(
"https://content.guardianapis.com/search?api-key=cb5c8f1d-41e3-4481-b13e-7b075cf3e537&show-fields=thumbnail,headline,byline,bodyText"
);
const results = response.data.response.results;
setNews(results);
// 카테고리 목록 추출
const uniqueCategories = [
// 새로운 Set 객체를 생성하여 중복을 제거한 카테고리 목록을 생성
...new Set(results.map((item) => item.sectionId)),
];
// 고유한 카테고리 목록을 상태로 저장
setCategories(uniqueCategories);
} catch (error) {
console.error(error);
}
};
fetchNews();
}, []);
// 카테고리 선택시 뉴스 필터링
const filteredNews =
selectedCategory === ""
? news
: news.filter((item) => item.sectionId === selectedCategory);
return (
<div>
<h2>뉴스</h2>
{/* 카테고리 필터 */}
<div className="flex gap-2 px-5 mb-4">
<button
onClick={() => setSelectedCategory("")}
className={`px-4 py-2 rounded ${
selectedCategory === "" ? "bg-blue-500 text-white" : "bg-gray-200"
}`}
>
전체
</button>
{categories.map((category) => (
<button
key={category}
onClick={() => setSelectedCategory(category)}
className={`px-4 py-2 rounded ${
selectedCategory === category
? "bg-blue-500 text-white"
: "bg-gray-200"
}`}
>
{category}
</button>
))}
</div>
<ul className="divide-y px-5">
{filteredNews.map((item) => (
<li key={item.id}>
<Link href={item.webUrl} className="py-3 flex gap-4">
<Image
src={item.fields.thumbnail}
alt={item.webTitle}
width={300}
height={200}
className="object-cover rounded w-1/3"
/>
<div className="flex-1 flex flex-col justify-between">
<strong>{item.webTitle}</strong>
<p className="text-gray-500 text-sm mt-2">
{item.webPublicationDate}
</p>
</div>
</Link>
</li>
))}
</ul>
</div>
);
};
export default NewsPage;
- results.map((item) => item.sectionId)
- 작업:
results
배열에서 각 객체의sectionId
값을 가져옵니다. - 결과:
map
함수는 새로운 배열을 반환하며, 배열의 각 요소는 sectionId 값이 됩니다.
const results = [
{ id: 1, sectionId: "sports", webTitle: "Sports News 1" },
{ id: 2, sectionId: "business", webTitle: "Business News 1" },
{ id: 3, sectionId: "sports", webTitle: "Sports News 2" },
];
const sectionIds = results.map((item) => item.sectionId);
console.log(sectionIds); // ["sports", "business", "sports"]
- new Set(results.map((item) => item.sectionId))
- 작업: 배열을 Set 객체로 변환합니다.
- 결과: Set은 중복된 값을 자동으로 제거하므로 고유한 값들만 남습니다.
const sectionIds = ["sports", "business", "sports"];
const uniqueCategories = [...new Set(sectionIds)];
console.log(uniqueCategories); // ["sports", "business"]
- [...new Set(results.map((item) => item.sectionId))]
- 작업:
Set
객체를 다시 배열로 변환합니다. - 결과: 고유한 값들을 포함한 배열이 생성됩니다.
...
는 스프레드 연산자로, Set의 각 요소를 배열에 펼칩니다.
const uniqueSet = new Set(["sports", "business"]);
const uniqueArray = [...uniqueSet];
console.log(uniqueArray); // ["sports", "business"]
'Front > React' 카테고리의 다른 글
최적화 - React 배우기 (0) | 2024.03.18 |
---|---|
React를 이용한 todo list app 만들기 (0) | 2024.03.14 |
React 카운터 앱 만들기 - component, useState (1) | 2024.03.04 |
React(리액트) 기본 기능 정리 (0) | 2024.02.29 |
API 통신 - react 배우기 (0) | 2023.11.16 |