일반적으로 React에서 컴포넌트 간에 데이터를 전달할 경우 props를 사용하여 부모 컴포넌트 -> 자식 컴포넌트로 데이터를 넘겨주고 이어받는 방식을 많이 사용하게 된다.
그런데 멀리 떨어진 컴포넌트로 데이터를 전달하거나, props를 사용하기 적절치 않은 경우가 있다.
그럴 때 유용하게 사용할 수 있는 Context API를 설명하고, 적용하여 프로젝트를 개선한 사례를 기록하려고 한다.
1. Context API의 개념
React Context API는 React 애플리케이션에서 전역 상태를 관리하고 컴포넌트 간에 데이터를 효율적으로 전달하기 위한 도구이다. Props를 계속해서 컴포넌트로 전달하는 번거로움을 줄이고, 상태 및 데이터를 보다 쉽게 공유할 수 있도록 도와주는 역할을 한다.
2. Context와 Provider,Consumer
Context는 데이터를 저장하고 컴포넌트 간에 공유하기 위한 저장소이다. Context는 createContext 함수로 생성하며, 하나의 Context 객체에는 Provider 컴포넌트와 Consumer 컴포넌트가 포함되어 있다.
const MyContext = React.createContext();
Provider 컴포넌트는 Context를 통해 전달할 데이터를 설정하는 역할을 한다. 이 데이터는 Provider 컴포넌트의 value prop에 전달된다.
<MyContext.Provider value={/* 데이터 */}>
{/* 자식 컴포넌트 */}
</MyContext.Provider>
Consumer 컴포넌트는 Provider 컴포넌트에서 제공한 데이터를 소비하기 위한 역할을 한다. Consumer 컴포넌트 내부에는 함수가 위치하며, 이 함수의 인자로 Context에서 제공한 데이터가 전달된다.
<MyContext.Consumer>
{data => (
// 데이터 활용
)}
</MyContext.Consumer>
3. useContext Hook
useContext 훅은 함수형 컴포넌트에서 Context를 더 쉽게 사용할 수 있도록 해주는 훅. 이를 사용하면 Consumer 컴포넌트를 사용하지 않고도 Context 데이터를 가져올 수 있다.
import React, { useContext } from 'react';
const data = useContext(MyContext);
활용 예시
진행중인 프로젝트에서 영화 상세 페이지에서 영화에 대한 댓글을 남기고, 그걸 수정하는 기능을 개발 중이었는데
'코멘트 수정'은 MidSection 컴포넌트에 존재하고, 수정된 코멘트는 BtmSection 컴포넌트에 실시간으로 반영되어야했다.
window.location.reload()를 사용해서 페이지를 강제 새로고침하여 전체 컴포넌트를 재렌더링 하는 방법도 있지만, 사용자 경험을 고려해봤을 때 적절한 방법은 아니라 생각되었고, props를 사용해서 수정된 댓글을 실시간으로 반영하려 했으나 섹션 컴포넌트마다 고유한 UI와 기능들이 존재했기 때문에
MidSection컴포넌트에서 BtmSection컴포넌트를 import 하여 props를 넘기면 UI와 기능들이 꼬여버리는 상황이 발생할 수 있어서 Context API를 사용하여 기능을 개선했다.
우선, movieDetailContext.js 파일을 만들어 Context를 생성하였다.
import { createContext, useContext, useState } from "react";
const movieDetailContext = createContext();
export const MovieProvider = ({ children }) => {
const [updatedComments, setUpdatedComments] = useState([]);
return (
<movieDetailContext.Provider
value={{ updatedComments, setUpdatedComments }}
>
{children}
</movieDetailContext.Provider>
);
};
export const useMovieContext = () => {
return useContext(movieDetailContext);
};
<movieDetailContext.Provider>로 해당하는 컴포넌트들을 감싸주기.
import { MovieProvider } from './MovieContext';
import MovieDetailTopSection from './MovieDetailTopSection';
import MovieDetailMidSection from './MovieDetailMidSection';
import MovieDetailBtmSection from './MovieDetailBtmSection';
const MovieDetailPage = () => {
// 영화 상세 페이지
return (
<MovieProvider>
<MovieDetailTopSection movieDetail={movieDetail} />
<MovieDetailMidSection movieDetail={movieDetail} posterUrl={posterUrl} ottInfo={ottInfo} />
<MovieDetailBtmSection movieDetail={movieDetail} />
</MovieProvider>
);
};
export default MovieDetailPage;
Context 사용
MidSection에서 댓글을 수정하는 handleUpdateComment 함수가 정상적으로 수행되면 useMovieContext로 관리되는 updateComments 상태가 변경된다.
import React, { useEffect, useState } from 'react';
import { useMovieContext } from './MovieContext';
const MovieDetailMidSection = ({ movieDetail, posterUrl, ottInfo }) => {
const { updatedComments, setUpdatedComments } = useMovieContext();
const handleUpdateComment = async () => {
// ... 댓글 수정 로직 ...
try {
const updatedComments = await getMovieCommentDB(movieId, movieSeq);
setUpdatedComments(updatedComments);
} catch (error) {
console.log('업데이트된 코멘트 가져오기 실패:', error);
}
};
// ... 컴포넌트 코드 ...
};
MovieDetailBtmSection 컴포넌트에서 useMovieContext hook을 사용하여 변경된 updatedComments 상태를 받아온다(수정된 댓글 반영)
import React, { useEffect, useState } from 'react';
import { useMovieContext } from './MovieContext';
const MovieDetailBtmSection = ({ movieDetail }) => {
const movieId = movieDetail.movieId;
const movieSeq = movieDetail.movieSeq;
const { updatedComments } = useMovieContext();
useEffect(() => {
if (updatedComments) {
setMovieCommentList(updatedComments);
}
}, [updatedComments]);
// ... 컴포넌트 코드의 나머지 부분 ...
};
export default MovieDetailBtmSection;
위처럼 Context API를 활용하여 서로 분리된 컴포넌트에서 변경된 데이터를 재렌더링하여 실시간으로 반영하는 기능을 수행할 수 있었다.
전역 데이터를 관리하거나 컴포넌트가 중첩되어 props를 사용하기 적절치 않다면 Context API가 훌륭한 대안이 될 수 있을 것 같다.
'React' 카테고리의 다른 글
[React] useRef의 장점 및 활용 (0) | 2024.02.20 |
---|---|
[React] 리액트 쿠키 사용법 (0) | 2023.07.27 |
[리액트] useEffect (0) | 2023.03.04 |
리액트 (React) (0) | 2023.02.22 |
댓글