본문 바로가기
반응형

React와 Next.js는 웹 개발에서 가장 인기 있는 프레임워크 중 하나이며, 동적인 웹 애플리케이션을 구축하기 위한 강력한 도구입니다. 하지만, 프로젝트 규모가 커지면서 여러 컴포넌트 사이에서 데이터를 공유하고 관리하는 것은 점점 더 복잡해집니다. 이러한 문제를 해결하기 위해 다양한 상태 관리 방법들이 등장했습니다.

본 글에서는 7가지 대표적인 상태 관리 방법 (useState, useReducer, Context API, Redux, Recoil, Zustand, Next.js SWR)을 심층 분석하고, 각 방법의 장단점을 비교하며 실제 사례와 함께 적용 방법을 제시하려고 합니다. 또한, 프로젝트 특성에 따라 적합한 상태 관리 방법을 선택하는데 도움이 되는 추가 고려 사항과 팁을 제공하려고 합니다.

1. useState: [로컬] 컴포넌트 내부 상태 관리

useState는 React에서 가장 기본적인 상태 관리 방법입니다. 컴포넌트 내부의 변수를 관리하는 데 적합하며, 간단하고 직관적인 사용이 가능합니다. 다만 컴포넌트 외부와는 변수를 공유할 수 없기 때문에 일반적으로는 전역 상태 관리 방법과 병행해서 로컬 변수 관리 용도로 주로 사용됩니다.

장점

  • 배우기 쉽고 사용하기 간편
  • 작은 컴포넌트의 상태 관리에 효율적
  • 코드를 간결하게 유지

단점

  • 컴포넌트 외부 상태 관리 불가능
  • 큰 규모 프로젝트에서는 관리 어려움
  • 중복된 코드 발생 가능성

사용 예시

const [count, setCount] = useState(0);  const handleClick = () => {   setCount(count + 1); };  return (   <div>     <h1>카운트: {count}</h1>     <button onClick={handleClick}>증가</button>   </div> ); 

2. useReducer: [로컬] 복잡한 상태 관리를 위한 강력한 도구

useReducer는 useState의 확장 버전으로, 여러 상태 변수를 하나의 리듀서 함수로 관리할 수 있습니다. 복잡한 상태 관리에 적합하며, 액션 기반 상태 변경을 통해 코드를 더욱 명확하고 이해하기 쉽게 만들 수 있습니다.

장점

  • 여러 상태 변수를 효율적으로 관리
  • 중복 코드 줄이고 액션 기반 상태 변경 가능
  • 테스트 및 디버깅 용이
  • 상태 흐름 추적 가능

단점

  • useState보다 복잡한 학습 곡선
  • 작은 컴포넌트에는 부담스러울 수 있음
  • 리듀서 함수 작성 및 관리 필요

사용 예시

const reducer = (state, action) => {   switch (action.type) {     case "INCREMENT":       return { ...state, count: state.count + 1 };     case "DECREMENT":       return { ...state, count: state.count - 1 };     default:       return state;   } };  const [state, dispatch] = useReducer(reducer, { count: 0 });  const handleClick = () => {   dispatch({ type: "INCREMENT" }); };  return (   <div>     <h1>카운트: {state.count}</h1>     <button onClick={handleClick}>증가</button>   </div> ); 

3. Context API: [전역] 전역 상태 관리를 위한 간편한 방법

Context API는 컴포넌트 트리 전역에서 상태를 공유할 수 있는 기본적인 방법입니다. props 드릴링을 방지하고 코드를 더욱 깔끔하게 유지할 수 있지만, 상태 업데이트 로직 추적이 어렵고 중복 렌더링 발생 가능성이 있습니다.

장점

  • 전역 상태 관리에 간편하고 직관적
  • props 드릴링 방지
  • 코드 재사용성 향상

단점

  • 상태 업데이트 로직 추적 어려움
  • 중복 렌더링 발생 가능성
  • 큰 규모 프로젝트에서는 관리 어려움

사용 예시

// Context API 관련 import import React, { createContext, useState, useContext } from "react";  // Context 생성 const MyContext = createContext(null);  // Provider 컴포넌트: Context 값 제공 const MyProvider = ({ children }) => {   const [count, setCount] = useState(0); // count state 및 setter 정의    return <MyContext.Provider value={{ count, setCount }}>{children}</MyContext.Provider>; };  // MyComponent 컴포넌트: Context 값 사용 const MyComponent = () => {   const { count } = useContext(MyContext); // Context에서 count 값 가져오기    return (     <div>       <h1>카운트: {count}</h1>     </div>   ); };  // App 컴포넌트: Provider 컴포넌트로 감싸서 MyComponent 렌더링 const App = () => {   return (     <MyProvider>       <MyComponent />     </MyProvider>   ); };  export default App; 

4. Redux: 대규모 프로젝트에서 강력한 상태 관리

Redux는 대규모 프로젝트에서 상태 관리를 위한 강력한 도구입니다. 예측 가능한 상태 흐름을 제공하고, 다양한 툴 및 라이브러리 지원으로 확장성이 뛰어나지만, 설정 및 학습 난이도가 높고 불필요한 오버헤드 발생 가능성이 있습니다.

장점

  • 예측 가능한 상태 흐름
  • 확장성 높고 다양한 툴 및 라이브러리 지원
  • 테스트 및 디버깅 용이
  • 큰 규모 프로젝트에 적합

단점

  • 설정 및 학습 난이도 높음
  • 불필요한 오버헤드 발생 가능성
  • 작은 규모 프로젝트에는 부담스러울 수 있음

사용 예시

// Redux 관련 import import { createStore, combineReducers, applyMiddleware } from "redux"; import { Provider } from "react-redux"; import { useSelector, useDispatch } from "react-redux";  // 리듀서 정의 const reducer = (state = { count: 0 }, action) => {   switch (action.type) {     case "INCREMENT":       return { ...state, count: state.count + 1 };     default:       return state;   } };  // Redux store 생성 const store = createStore(reducer);  // MyComponent 컴포넌트 const MyComponent = () => {   // useSelector를 사용하여 store에서 count state 가져오기   const count = useSelector((state) => state.count);    // useDispatch를 사용하여 dispatch 함수 가져오기   const dispatch = useDispatch();    // handleClick 함수: dispatch를 사용하여 INCREMENT action 발생시키기   const handleClick = () => {     dispatch({ type: "INCREMENT" });   };    return (     <div>       <h1>카운트: {count}</h1>       <button onClick={handleClick}>증가</button>     </div>   ); };  // App 컴포넌트 const App = () => {   return (     // Provider 컴포넌트로 store 전달     <Provider store={store}>       <MyComponent />     </Provider>   ); };  export default App; 

5. Recoil: Redux의 간편함과 Context API의 성능을 결합

Recoil은 Redux의 간편함과 Context API의 성능을 결합한 새로운 상태 관리 라이브러리입니다. 자동 렌더링 최적화 기능을 제공하며 기본적인 상태 관리에 효율적이지만, Redux만큼 강력하지 않고 커뮤니티 및 지원 도구 상대적으로 부족합니다.

장점

  • 배우기 쉽고 사용하기 간편
  • 자동 렌더링 최적화
  • 기본적인 상태 관리에 효율적

단점

  • Redux만큼 강력하지 않음
  • 커뮤니티 및 지원 도구 상대적으로 부족
  • 큰 규모 프로젝트에는 적합하지 않을 수 있음

사용 예시

// Recoil 관련 import import { atom, useRecoilValue, useRecoilSetter } from "recoil";  // Recoil atom 정의 const countAtom = atom({   key: "count", // atom의 고유 키   default: 0, // 초기값 });  // MyComponent 컴포넌트 const MyComponent = () => {   // useRecoilValue를 사용하여 countAtom의 값 가져오기   const count = useRecoilValue(countAtom);    // useRecoilSetter를 사용하여 countAtom 값 설정 함수 가져오기   const setCount = useRecoilSetter(countAtom);    // handleClick 함수: setCount 함수 사용하여 count 값 증가   const handleClick = () => {     setCount(count + 1);   };    return (     <div>       <h1>카운트: {count}</h1>       <button onClick={handleClick}>증가</button>     </div>   ); };  // App 컴포넌트 const App = () => {   return (     // RecoilRoot 컴포넌트로 감싸서 Recoil 사용     <RecoilRoot>       <MyComponent />     </RecoilRoot>   ); };  export default App; 

6. Zustand: 간편하고 가벼운 상태 관리

Zustand는 Zustand 패턴을 기반으로 하는 상태 관리 라이브러리입니다. 매우 간편하고 가벼우며, TypeScript 지원 및 React Hook과의 완벽한 통합을 제공하지만, 다른 라이브러리만큼 강력하지 않고 커뮤니티 및 지원 도구도 상대적으로 부족합니다.

장점

  • 매우 간편하고 가벼움
  • TypeScript 지원
  • React Hook과 완벽하게 통합

단점

  • 다른 라이브러리만큼 강력하지 않음
  • 커뮤니티 및 지원 도구 상대적으로 부족
  • 큰 규모 프로젝트에는 적합하지 않을 수 있음

사용 예시

// Zustand 관련 import import { createStore, useStore } from "zustand";  // Zustand store 생성 const store = createStore(() => ({   // 상태 정의   count: 0, }));  // MyComponent 컴포넌트 const MyComponent = () => {   // useStore를 사용하여 store 접근   const { count, increment } = useStore(store);    return (     <div>       <h1>카운트: {count}</h1>       <button onClick={increment}>증가</button>     </div>   ); };  // App 컴포넌트 const App = () => {   return (     // ZustandProvider 컴포넌트로 store 전달     <ZustandProvider store={store}>       <MyComponent />     </ZustandProvider>   ); };  export default App; 

7. Next.js SWR: 데이터 페칭 및 캐싱을 위한 최적화된 솔루션

Next.js SWR은 Next.js에서 데이터 페칭 및 캐싱을 위한 최적화된 솔루션입니다. 데이터 폴링, 서버 측 렌더링 지원, 자동 재시도 등 다양한 기능을 제공하지만, 다른 라이브러리만큼 다양한 기능을 제공하지 않고, Next.js 환경에서만 사용할 수 있습니다.

장점

  • 데이터 페칭 및 캐싱을 위한 최적화
  • 데이터 폴링, 서버 측 렌더링 지원
  • 자동 재시도

단점

  • 다른 라이브러리만큼 다양한 기능 제공하지 않음
  • Next.js 환경에서만 사용 가능

사용 예시

// Next.js SWR 관련 import import { useSWR } from "swr";  // API 주소 const apiUrl = "/api/users";  // MyComponent 컴포넌트 const MyComponent = () => {   // SWR hook 사용하여 API 데이터 가져오기   const { data, error } = useSWR(apiUrl);    // 에러 처리   if (error) return <div>에러 발생</div>;    // 데이터 로딩 중 처리   if (!data) return <div>로딩 중...</div>;    // 데이터 출력   return (     <div>       {data.users.map((user) => (         <div key={user.id}>           <h1>{user.name}</h1>         </div>       ))}     </div>   ); };  // App 컴포넌트 const App = () => {   return (     <div>       <MyComponent />     </div>   ); };  export default App; 

선택 가이드라인

다양한 상태 관리 방법 중 어떤 방법을 선택할지는 프로젝트의 규모, 복잡성, 개발자의 경험과 학습곡선, 팀환경 및 협업 방식 등을 고려해야 합니다.

  • 단일 컴포넌트 : useState, useReducer
  • 간단한 프로젝트: Context API 또는 Next.js SWR
  • 중간 규모 프로젝트: Zustand 또는 Recoil
  • 대규모 프로젝트: Redux

UX 공작소

UX와 UI에 관해 내가 알게된 다양한 이야기를 공유해요~