의도: 상태 관리의 목적과 평소 상태 관리 패턴 파악 및 전역 상태 관리에 대해서도 추가 답변(사용하는 라이브러리 등)을 기대하는 질문
나의 답안
상태 관리는 React의 핵심 철학인 단일 방향 데이터 흐름을 기반으로, 컴포넌트의 데이터와 동작을 일관성 있고 예측 가능하게 만드는 데 중요한 역할을 합니다.
리액트에서 상태 관리를 해야 하는 이유는 다음과 같습니다.첫째, 애플리케이션의 데이터 흐름을 제어하여 데이터의 일관성을 보장하기 위해서입니다.
리액트는 단일 방향 데이터 흐름을 따르므로, 상태가 한 방향으로만 전달됩니다.
이를 통해 애플리케이션 데이터가 예측 가능한 방식으로 관리되고 데이터 불일치 문제를 방지할 수 있습니다.둘째, 사용자 인터페이스(UI)를 데이터 변경에 따라 동기화하기 위해서입니다.
리액트 애플리케이션은 사용자의 상호작용, API 호출, 혹은 이벤트 처리에 따라 동적으로 UI가 업데이트되어야 합니다.
상태가 변경되면 React가 컴포넌트를 다시 렌더링해 데이터와 UI가 항상 동기화됩니다.상태를 관리하기 위해서는 다양한 방법을 사용할 수 있습니다.
컴포넌트 내부에서는useState를 사용하여 상태를 관리할 수 있습니다.
이는 단일 컴포넌트의 상태를 관리할 때 적합합니다.두 개 이상의 컴포넌트가 동일한 데이터를 공유해야 할 때는 상태를 상위 컴포넌트로 올려서 관리해야 합니다.
리액트는 상태를 부모 컴포넌트에 올려서 하위 컴포넌트에 props로 전달하도록 권장합니다.전역 상태를 관리할 때는 Context API나 외부 라이브러리를 사용할 수 있습니다.
대표적인 상태 관리 라이브러리로는 Redux, Zustand, Recoil 등이 있습니다.
주어진 답안 (모범 답안)
상태관리의 필요성은 리액트의 MVVM 패턴과 관련지어 설명할 수 있습니다.
Model-View-ViewModel 패턴에 따라 상태는 Model에, 화면 UI는 View에 역할을 위임함으로써 서로의 독립성을 확보할 수 있습니다.
이에 따라 개발자는 각각을 다른 관점에서 파악함으로써 각자의 기능에 집중해 개발할 수 있는 환경을 구축할 수 있습니다.저의 경우에는 이를 실현하기 위한 하나의 방법으로 커스텀 훅을 사용하여 Model 부분의 재사용성과 독립성을 향상시켰습니다.
또한 하나의 상태를 여러 컴포넌트가 바라볼 때 나타나는 props-drilling과 같은 문제를 해결하기 위해 Redux 등의 전역 상태 관리 도구를 사용한 경험이 있습니다.
React의 상태 관리는 컴포넌트의 상태(state)를 효율적으로 관리하고, 상태 변경에 따라 UI를 동적으로 업데이트하기 위해 사용하는 핵심 개념이다.
React는 단일 방향 데이터 흐름(unidirectional data flow)을 따르기 때문에 상태를 잘 관리하는 것이 중요한 역할을 한다.
React에서 상태 관리를 하는 이유는 애플리케이션의 데이터 흐름을 제어하고, 사용자 인터페이스(UI)를 데이터 변경에 따라 동기화하기 위함이다.
상태 관리는 React의 핵심 철학인 단일 방향 데이터 흐름을 기반으로, 컴포넌트의 데이터와 동작을 일관성 있고 예측 가능하게 만드는 데 중요한 역할을 한다.
useState로그인 시스템
로그인 상태와 사용자 정보를 전역 상태로 관리하여 모든 컴포넌트에서 참조 가능
쇼핑 카트
사용자가 추가한 아이템 목록을 상태로 관리하여, 전역에서 접근하거나 업데이트
다크 모드 전환
사용자 테마 설정을 Context API로 관리하여 전역에서 적용 가능
여행 계획
일정, 장소, 예약 정보를 관리하여 여행 계획 페이지와 관련 컴포넌트 간 상태를 공유
컴포넌트 내에서 관리 (Local State)
useState를 사용하여 상태를 관리하는 방식이다.예제
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>증가</button>
</div>
);
}
컴포넌트 간 상태 공유 (Lifting State Up)
예제
function Parent() {
const [sharedState, setSharedState] = useState("");
return (
<div>
<Child1 setSharedState={setSharedState} />
<Child2 sharedState={sharedState} />
</div>
);
}
function Child1({ setSharedState }) {
return <input onChange={(e) => setSharedState(e.target.value)} />;
}
function Child2({ sharedState }) {
return <p>입력된 값: {sharedState}</p>;
}
Context API를 통한 상태 관리 (Global State)
예제
import React, { createContext, useContext, useState } from "react";
const MyContext = createContext();
function Parent() {
const [value, setValue] = useState("Hello, Context!");
return (
<MyContext.Provider value={{ value, setValue }}>
<Child />
</MyContext.Provider>
);
}
function Child() {
const { value, setValue } = useContext(MyContext);
return (
<div>
<p>{value}</p>
<button onClick={() => setValue("Updated Value")}>Update</button>
</div>
);
}
외부 라이브러리를 사용한 상태 관리
1) Redux
예제
import { createStore } from "redux";
import { Provider, useSelector, useDispatch } from "react-redux";
const initialState = { count: 0 };
function reducer(state = initialState, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
default:
return state;
}
}
const store = createStore(reducer);
function Counter() {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
</div>
);
}
export default function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}
2) Zustand
예제
import create from "zustand";
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
function Counter() {
const { count, increment } = useStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
</div>
);
}
3) Recoil
예제
import { atom, selector, useRecoilState, RecoilRoot } from "recoil";
const countState = atom({
key: "countState",
default: 0,
});
function Counter() {
const [count, setCount] = useRecoilState(countState);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
export default function App() {
return (
<RecoilRoot>
<Counter />
</RecoilRoot>
);
}