React에서는 컴포넌트 내부에서 상태를 관리하기 위해 useState와 useReducer와 같은 훅(Hook)을 제공합니다. 이러한 상태 관리 방식은 로컬 상태 관리라고도 하며, 컴포넌트 내부의 상태를 독립적으로 관리하고 업데이트할 수 있습니다.
const [count, setCount] = useState(0);
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
const [state, dispatch] = useReducer(reducer, initialState);
이러한 훅들은 컴포넌트의 상태가 자식 컴포넌트로 전달되지 않는 경우에 매우 효과적입니다.
Context API는 React에서 전역 상태를 관리하기 위해 제공하는 내장 도구입니다. 여러 컴포넌트들이 공통적으로 사용하는 데이터를 상위 컴포넌트에서 하위 컴포넌트로 쉽게 전달할 수 있게 해줍니다. 이를 통해 props drilling 문제를 해결할 수 있습니다.
props drilling이란 부모에서 자식 컴포넌트로 props를 계속 전달해야 하는 상황을 말합니다. 이러한 상황이 복잡해질수록 관리가 어려워지기 때문에 Context API가 필요한 경우가 많습니다.
Context API를 사용하기 위해서는 React.createContext() 함수를 사용하여 Context 객체를 생성한 후, Provider와 Consumer를 사용합니다.
Context 생성: Context 객체를 생성합니다.
const ThemeContext = React.createContext();
Provider 설정: Context 값을 제공하기 위해 Provider를 사용합니다.
function App() {
return (
<ThemeContext.Provider value={"dark"}>
<Toolbar />
</ThemeContext.Provider>
);
}
Consumer 사용: 하위 컴포넌트에서 Context 값을 사용하기 위해 Consumer를 사용합니다.
function Toolbar() {
return (
<ThemeContext.Consumer>
{value => <div>Current theme: {value}</div>}
</ThemeContext.Consumer>
);
}
최근에는 useContext 훅을 사용하여 더 간편하게 Context 값을 사용할 수 있습니다.
import { useContext } from 'react';
function Toolbar() {
const theme = useContext(ThemeContext);
return <div>Current theme: {theme}</div>;
}
컴포넌트 외부에서 전역 상태를 관리하는 것은 여러 컴포넌트들이 상태를 공유해야 할 때 매우 유용합니다. 이를 위해 보통 상태 관리 라이브러리를 사용합니다. 가장 널리 알려진 라이브러리는 Redux와 Redux Toolkit입니다.
설치: Redux와 Redux Toolkit을 설치합니다.
npm install @reduxjs/toolkit react-redux
Store 생성: 상태를 저장할 중앙 Store를 생성합니다.
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: {
// 여기에 리듀서를 추가합니다.
},
});
Slice 생성: 상태와 리듀서 로직을 정의하는 slice를 생성합니다.
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => {
state.value += 1;
},
decrement: state => {
state.value -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
Provider로 Store 연결: React 애플리케이션에 Store를 제공하기 위해 Provider를 사용합니다.
import { Provider } from 'react-redux';
import store from './store';
function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}
컴포넌트에서 상태 사용: 상태를 사용하려면 useSelector와 useDispatch 훅을 사용합니다.
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
</div>
);
}
Props Drilling은 부모 컴포넌트에서 여러 단계의 자식 컴포넌트로 props를 전달해야 할 때 발생하는 문제를 말합니다. 중간 단계의 컴포넌트들은 실제로 필요한 데이터가 아닌 단지 전달자 역할만 하게 됩니다. 이로 인해 코드의 복잡도가 증가하고 유지보수가 어려워질 수 있습니다.
전역 상태 관리 도구를 사용하면 이러한 props drilling 문제를 해결할 수 있습니다. 중앙에서 상태를 관리하고 필요한 컴포넌트에서 상태를 구독하도록 함으로써, 코드의 간결성과 유지보수성을 높일 수 있습니다. 전역 상태 관리 도구를 사용하면 여러 컴포넌트 간 상태 공유가 쉬워지고, 상태의 일관성을 유지하기가 훨씬 수월합니다.
React 내부와 외부에서의 상태 관리 방법을 잘 이해하면, 애플리케이션의 복잡도에 맞게 적절한 상태 관리 전략을 선택할 수 있습니다. 간단한 로컬 상태는 useState나 useReducer로, 전역 상태가 필요할 때는 Context API, Redux, 혹은 Redux Toolkit과 같은 도구를 사용하여 보다 효율적으로 관리할 수 있습니다.