Redux
애플리케이션의 상태(state)를 하나의 중앙 저장소(store)에 관리합니다. 이를 통해 상태를 보다 체계적으로 관리하고, 애플리케이션의 동작을 예측 가능하게 만듭니다.
주요 특징
1) 단일 저장소: 애플리케이션의 모든 상태는 하나의 저장소 안에 있는 객체 트리에 저장됩니다.
2) 불변성: 상태는 직접 수정되지 않고, 새로운 상태를 생성하여 변경됩니다.
3) 액션(Action): 상태를 변경하기 위해 "무엇이 일어날지"를 서술하는 객체를 보냅니다.
4) 리듀서(Reducer): 액션을 받아 상태 트리를 어떻게 변경할지 명시하는 순수 함수입니다
개념
1) Store (저장소)
애플리케이션의 상태를 저장하는 객체입니다.
createStore() 함수를 사용하여 생성합니다.
import { createStore } from 'redux';
let store = createStore(counter);
2) Action (액션)
상태를 변경하기 위해 보내는 객체입니다.
액션은 반드시 type 속성을 포함해야 하며, 추가적인 데이터를 포함할 수 있습니다.
javascript const action = { type: 'INCREMENT' };
3) Reducer (리듀서)
액션을 받아 새로운 상태를 반환하는 순수 함수입니다. 이전 상태와 액션을 입력으로 받아 새로운 상태를 반환합니다.
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
투두리스트 구현 단계
1) Redux 저장소 생성
먼저, Redux 저장소를 생성하고 React 애플리케이션에 연결합니다. Provider 컴포넌트를 사용하여 저장소를 애플리케이션 전체에 전달합니다.
import React from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './components/App';
import rootReducer from './reducers';
// Redux 저장소 생성
const store = createStore(rootReducer);
// React 애플리케이션에 Redux 연결
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
2) 리듀서 작성
(1) todos 리듀서 투두 항목의 추가와 완료 상태 토글을 처리하는 리듀서를 작성합니다.
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false,
},
];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
default:
return state;
}
};
export default todos;
(2) visibilityFilter
리듀서 투두 항목의 필터링 상태(모두 보기, 완료된 항목만 보기, 활성 항목만 보기)를 관리합니다
import { VisibilityFilters } from '../actions';
const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
};
export default visibilityFilter;
(3) 리듀서 결합 combineReducers를 사용하여 여러 리듀서를 하나로 결합합니다.
import { combineReducers } from 'redux';
import todos from './todos';
import visibilityFilter from './visibilityFilter';
export default combineReducers({
todos,
visibilityFilter,
});
이런 식으로 리듀서를 사용하면 됩니다.
Redux Toolkit으로 효율적인 상태 관리하기
Redux 개발을 단순화하고, 보일러플레이트 코드를 줄이며, 모범 사례를 쉽게 따를 수 있도록 도와주는 공식 라이브러리입니다. 기존 Redux의 복잡한 설정 과정을 간소화하고, 더 직관적인 API를 제공합니다
1) configureStore Redux 스토어를 설정하는 함수입니다.
기존 createStore보다 간단하며, 미들웨어와 DevTools 설정도 자동으로 처리합니다.
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export default store;
3. 주요 API와 사용법
1) configureStore Redux 스토어를 설정하는 함수입니다. 기존 createStore보다 간단하며, 미들웨어와 DevTools 설정도 자동으로 처리합니다.
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export default store;
3) createAsyncThunk 비동기 액션을 생성하는 함수
API 호출 같은 비동기 작업에 유용합니다.
import { createAsyncThunk } from '@reduxjs/toolkit';
export const fetchUser = createAsyncThunk(
'user/fetchUser',
async (userId, thunkAPI) => {
const response = await fetch(`/api/user/${userId}`);
return response.json();
}
);
4) Provider로 스토어 연결
React 애플리케이션에서 Redux Store를 사용하려면 Provider로 스토어를 연결해야 합니다.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
5) useSelector와 useDispatch
React 컴포넌트에서 상태를 조회하거나 액션을 디스패치할 때 사용하는 훅입니다.
import React from 'react';
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(increment())}>+</button>
<span>{count}</span>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
export default Counter;
로그인 시 비동기처리와 상태관리
1. 로그인 요청
2. 서버 응답
3. 응답받은 유저 정보를 전역 스토어에 저장
=> 컴포넌트의 useEffect에서 API 호출하고 응답 받은 결과를 스토어에 업데이트을 합니다.
dispatch({type:"spdateUser":payload:{nickname:김코딩, purchased:패키지}}
주의할 점은 reducer는 순수 함수이기 때문에 user store의 reducer안에 API를 호출하면 안됩니다.
(순수함수 : 동일한 인자가 주어졌을 때 항상 동일한 결과를 반환하는 함수입니다.)
하지만 위에 있는 방식은 화면이 복잡해지면 안 좋은 코드가 되기 때문에 middleware를 사용하면 됩니다.
middleware 사용 전
1. action 객체를 dispatch
2. Reducer
3. Store업데이트
middleware 사용 후
1. action 객체를 dispatch
2. middleware
3. Reducer
4. Store업데이트
=> aciton 객체를 dispatch하고, reducer가 해당 action 객체에 대해 Store를 업데이트하기 전에 추가적인 작업을 할 수 있도록 도와줍니다. 예를 들어 비동기처리, 로깅 등이 있습니다.
redux-thunk
dispatch에 action 객체가 아닌 thunk 함수를 전달합니다.
index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import rootReducer from "./modules/index";
import App from "./App";
const store = createStore(rootReducer, applyMiddleware(thunk));
// 스토어를 만들때 리듀서를 만들면서 어떤 미들에어를 적용할 지 sotre에 저장합니다.
ReactDOM.render(
<Provider store={store}> // Provider에 스토어를 넣어줍니다.
<App />
</Provider>,
document.getElementById("root")
);
accoount.js
Redux에서 상태를 변경하기 위해 액션 타입과 액션 생성 함수를 정의합니다.
import { fetchUser } from "./api";
// 액션
const FETCH_USER_REQUEST = "FETCH_USER_REQUEST";
const FETCH_USER_SUCCESS = "FETCH_USER_SUCCESS";
const FETCH_USER_FAILURE = "FETCH_USER_FAILURE";
// 액션 생성 함수
export const fetchUserRequest = () => ({
type: FETCH_USER_REQUEST,
});
export const fetchUserSuccess = ({ name, email }) => ({
type: FETCH_USER_SUCCESS,
payload: { name, email },
});
export const fetchUserFailure = () => ({
type: FETCH_USER_FAILURE,
});
// TODO: thunk 함수 만들기
export const fetchUserThunk = () => {};
//Redux 리듀서는 상태(state)를 변경하는 순수 함수입니다.
//
const initialState = {
loading: false,
name: "",
email: "",
};
//각 액션 타입에 따라 상태를 업데이트합니다:
export default function counter(state = initialState, action) {
switch (action.type) {
case FETCH_USER_REQUEST:
return {
...state,
loading: true,
};
case FETCH_USER_SUCCESS:
return {
loading: false,
name: action.payload.name,
email: action.payload.email,
};
case FETCH_USER_SUCCESS:
return initialState;
default:
return state;
}
}
api.js
export const fetchUser = () => {
return new Promise((resolve) => {
setTimeout(
() => resolve({ name: "hwarari", email: "hwarari@gmail.com" }),
2000
);
});
};
app.js
React와 Redux를 연동하여 애플리케이션을 구성하는 기본적인 설정을 보여줍니다
import { useSelector, useDispatch } from "react-redux";
import { fetchUser } from "../src/modules/account/api";
import {
fetchUserRequest,
fetchUserSuccess,
fetchUserFailure,
} from "./modules/account/account";
function App() {
const account = useSelector((state) => state.account); // 전역 상태의 값을 가져옵니다.
const { loading, name, email } = account;
const dispatch = useDispatch();
const handleClick = async () => {
dispatch(fetchUserRequest());
try {
const res = await fetchUser();
dispatch(fetchUserSuccess({ name: res.name, email: res.email }));
} catch {
dispatch(fetchUserFailure());
}
};
return (
<div className="App">
<button onClick={handleClick}>User 정보 가져오기</button>
{loading ? (
<p>loading...</p>
) : name && email ? (
<>
<p>이름 : {name}</p>
<p>이메일 : {email}</p>
</>
) : null}
</div>
);
}
export default App;
위에 있는 코드에서 thunk 함수를 만들어보시죠
app.js
fetchUserThunk 호출
import {
fetchUserRequest,
fetchUserSuccess,
fetchUserFailure,
fetchUserThunk
} from "./modules/account/account";
accoount.js
Redux Thunk 미들웨어를 활용하여 비동기 작업을 처리하는 액션 생성 함수
export const fetchUserThunk = () => {
return async (dispatch) => {
try {
dispatch(fetchUserRequest()); // 요청 시작 액션 디스패치
const res = await fetchUser(); // 비동기 API 호출
dispatch(fetchUserSuccess({ name: res.name, email: res.email })); // 성공 액션 디스패치
} catch {
dispatch(fetchUserFailure()); // 실패 액션 디스패치
}
};
};
app.js
Thunk 함수는 컴포넌트에서 사용
const handleClick = () => {
dispatch(fetchUserThunk());
};
Recoil
리액트 팀에서 직접 만든 상태관리 라이브러리입니다.
비동기 데이터 통신을 위한 기능 제공합니다.
React 내부에 접근이 가능하여 동시성 모드, Suspense 등을 손쉽게 지원 가능합니다.
index.js
RecoilRoot로 애플리케이션을 감싸야 Recoil 상태를 사용할 수 있습니다.
import React from 'react';
import ReactDOM from 'react-dom';
import { RecoilRoot } from 'recoil';
import App from './App';
ReactDOM.render(
<RecoilRoot>
<App />
</RecoilRoot>,
document.getElementById('root')
);
atoms.js
atom을 사용해 상태를 정의합니다.
import { atom } from 'recoil';
// 카운터 상태 정의
export const counterState = atom({
key: 'counterState', // 고유한 키
default: 0, // 초기값
});
App.js
Recoil의 useRecoilState 훅을 사용하여 상태를 읽고 업데이트합니다.
import React from 'react';
import { useRecoilState } from 'recoil';
import { counterState } from './atoms';
function App() {
const [count, setCount] = useRecoilState(counterState);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return (
<div>
<h1>Recoil Counter</h1>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
export default App;
'리액트' 카테고리의 다른 글
[React] useCallback, Meomoization (0) | 2025.04.29 |
---|---|
[TypeScript] 타입스크립트 정복하기 (1) | 2025.04.18 |
[React] 렌더링 최적화 (1) | 2025.04.16 |
[React] Reate Query와 Suspense (0) | 2025.04.15 |
[React] React Context API를 활용한 전역 상태 관리 (1) | 2025.04.14 |