Redux Toolkit은 Redux를 더욱 쉽게 사용하기 위해 만들어졌다. 쉽게 만들어졌다고? 그럼 쓰지 않을 수가 없다.
그 전에 Redux Toolkit이 나오게 된 이유를 보도록 하자.
Redux의 문제점
- 리덕스의 복잡한 스토어 설정
- 리덕스를 효율적으로 사용하기 위해서 설치해야되는 많은 패키지들(redux-action, redux-thunk 등)
- 리듀서(reducer)에 들어가는 필요하지만? 불필요해보이는 많은 양의 코드들(상용구 코드)
위의 문제점들을 개선한 것이 React Toolkit 이라고 보면 된다.
그럼 이제 어떻게 하면 되는지 알아보도록 하자.
예제로는 이전에 Redux 할 때 했던 Counter와 로그인 폼을 섞을 예정이다.
둘이 연관성은 딱히 없지만 왜? 슬라이스 마다 파일을 나누고 하나의 스토어에서 관리하는 법을 보여주기 위함이다.
핵심 내용만 보고 싶다면 아래부분의 index.js 설명 부분부터 보면 되겠다.
일단 패키지 설치부터 해야한다.
npm install redux react-redux @reduxjs/toolkit
패키지 설치 후 로그인 폼 페이지를 만든다.
Login.module.css
.login {
margin: 5rem auto;
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2);
width: 25rem;
border-radius: 8px;
padding: 1rem;
text-align: center;
background-color: #f4f0fa;
}
.control {
margin-bottom: 0.5rem;
}
.control label {
display: block;
color: #616161;
text-transform: uppercase;
margin-bottom: 0.5rem;
}
.control input {
display: block;
width: 20rem;
margin: auto;
border-radius: 4px;
padding: 0.25rem;
border: 1px solid #ccc;
}
Login.js
import { authActions } from "../store/auth";
import { useDispatch } from "react-redux";
import classes from "./Login.module.css";
const Login = () => {
const dispatch = useDispatch();
const loginHandler = (event) => {
event.preventDefault();
dispatch(authActions.login());
};
return (
<>
<main className={classes.login}>
<section>
<form onSubmit={loginHandler}>
<div className={classes.control}>
<label htmlFor="email">Email</label>
<input type="email" id="email" />
</div>
<div className={classes.control}>
<label htmlFor="password">Password</label>
<input type="password" id="password" />
</div>
<button>Login</button>
</form>
</section>
</main>
</>
);
};
export default Login;
이메일, 패스워드를 입력하면 로그인된다. (사실 그냥 눌러도 로그인되지만..)
로그인 버튼을 클릭하면 useDispatch를 사용하여 import 된 authActions의 login 메소드를 호출한다.
여기서 authActions는 auth slice 관련해서 reducers의 메소드들을 담고 있다고 보면 된다.
잘 이해가 안갈 수 있지만 아래 Redux-toolkit 부분 설명까지 보고 다시 올라와서 보면 이해가 갈 것이다.
그리고 지금은 에러가 날 것이다. 왜냐하면 아직 store 관련해서 구현하지 않았기 때문이다!
로그인 했을 때의 페이지를 만들어보자.
UserProfile.module.css
.profile {
margin: 5rem auto;
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2);
width: 40rem;
border-radius: 8px;
padding: 1rem;
text-align: center;
background-color: #f4f0fa;
}
UserProfile.js
import { useSelector } from "react-redux";
import classes from "./UserProfile.module.css";
const UserProfile = () => {
const emailInfo = useSelector((state) => state.auth.email);
return (
<main className={classes.profile}>
<h2>Hello, {emailInfo}</h2>
</main>
);
};
export default UserProfile;
로그인 하고 나서 useSelector를 이용하여 간단하게 로그인한 사용자의 이메일 정보를 뿌려줬다.
이전에 Redux 설명할 때 했던 counter.js도 Redux-toolkit에 맞게 변경을 해보도록 하려한다.
Counter.module.css
.counter {
margin: 5rem auto;
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2);
width: 40rem;
border-radius: 8px;
padding: 1rem;
text-align: center;
background-color: #f4f0fa;
}
.counter h1 {
text-transform: uppercase;
color: #575757;
margin: 0;
font-size: 1rem;
}
.value {
font-size: 2rem;
color: #3c0080;
margin: 2rem 0;
font-weight: bold;
}
.counter button {
margin: 1rem;
}
Counter.js
import { useSelector, useDispatch } from "react-redux";
import { couterActions } from "../store/counter";
import classes from "./Counter.module.css";
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector((state) => state.counter.counter);
const showCounter = useSelector((state) => state.counter.showCounter);
const incrementHandler = () => {
dispatch(couterActions.increment());
};
const increaseHandler = () => {
dispatch(couterActions.increase(5));
};
const decrementHandler = () => {
dispatch(couterActions.decrement());
};
const toggleCounterHandler = () => {
dispatch(couterActions.toggleCounter());
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
{showCounter && <div className={classes.value}>{counter}</div>}
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={increaseHandler}>Increase by 5</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
각 버튼 이벤트마다 useDipatch와 store의 counterActions를 활용하여 메소드를 호출 했다.
여기서도 counterActions는 counter 관련 slice의 reducers의 정보를 담고 있다고 보면 된다.
폼 관련해서는 마지막으로 App.js 파일을 보도록 하자.
App.js
import { useSelector } from "react-redux";
import Counter from "./components/Counter";
import Login from "./components/Login";
import UserProfile from "./components/UserProfile";
function App() {
const isAuth = useSelector((state) => state.auth.isAuthenticated);
return (
<>
{!isAuth && <Login />}
{isAuth && <UserProfile />}
<Counter />
</>
);
}
export default App;
useSelector를 사용하여 auth store에 있는 isAuthenticated 의 정보를 가져와서 false 값이면 로그인페이지를, true이면 유저 프로파일 정보를 보여주도록 했다.
자 길고 길었다..!! 이제 React-Toolkit에 관해서 작성할 차례다.
src 밑에 store 폴더를 만들어주고 auth.js 파일을 만든다.
(auth.js에 웬만한 설명을 다 적으려고 한다. 중요!!)
auth.js
import { createSlice } from "@reduxjs/toolkit";
const initialAuthState = { isAuthenticated: false, email: null };
const authSlice = createSlice({
name: "authentication",
initialState: initialAuthState,
reducers: {
login(state, action) {
state.isAuthenticated = true;
state.email = action.payload.email;
},
logout(state) {
state = initialAuthState;
},
},
});
export const authActions = authSlice.actions;
export default authSlice.reducer;
createSlice 함수는 선언한 slice의 name에 따라서 액션 생성자, 액션 타입, 리듀서를 자동으로 생성해준다. 따라서 별도로 createAction이나 createReducer를 사용하지 않아도 된다.
name은 Reducer의 이름을 정하는거고
initialState는 데이터의 초기 값을 세팅하는 용도이며
reducers는 이제 상태가 변하면 어떻게 실행될지 정하는 부분이다.
initialState에서 isAuthenticated 는 인증했는지 여부, email은 로그인 했을 때 정보를 뿌려주기 위해 가지고 있고 초기 값은 null로 설정 해줬다.
여기서 한가지 더 봐야될 점은 redux에서는 아래와 같이 선언해주었는데
if (type === 'login') {
return {
isAuthenticated: true,
email: action.email
};
}
이 부분이 위처럼 간결하게 메소드 처럼 변경되었다. 또 다른점은 redux는 state의 값을 직접 변경하면 안됐는데 toolkit을 사용하면 직접 변경해줘도 영향을 미치지 않는다.
내부 값을 변경 해줄 때 dispatch({email:aaa@naver.com}) 이런식으로 보내주면
위와같이 action.payload.email로 받아줘야한다. 중간에 payload가 추가된다!
만약 그냥 dispatch(10) 이런식으로 보내면 그냥 action.payload로 받으면 된다.
authActions 변수는 나중에 로그인할 때 dispatch 해주면서 slice의 reducers 부분에 정의한 메소드에 접근하기 위함이고
authSlice.reducer을 export 한 이유는 나중에 index.js에 reducer에 대한 정보를 스토어에 저장해 주기 위함이다.
그다음은 counter의 slice 작업을 해보도록 하자.
counter.js
import { createSlice } from "@reduxjs/toolkit";
const initialCounterState = { counter: 0, showCounter: true };
const counterSlice = createSlice({
name: "counter",
initialState: initialCounterState,
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase(state, action) {
state.counter = state.counter + action.payload;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
},
},
});
export const couterActions = counterSlice.actions;
export default counterSlice.reducer;
여기선 추가 설명 할 것은 딱히 없기에 위의 auth.js 부분의 설명을 참고하면 될 것 같다.
자 이제 같은 store 내부에 index.js 파일을 만들어 준다. 이 파일은 auth와 counter의 reducer를 store에서 통합하려고 하기 위함이다.
index.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counter";
import authReducer from "./auth";
const store = configureStore({
reducer: {
counter: counterReducer,
auth: authReducer,
},
});
export default store;
configureStore는 reducer 들에 대한 정보를 담고 있는다.
useSelector 기준으로 접근 할 때는 예를 들어 auth의 isAuthenticated의 값을 받고 싶다면 아래와 같이 configureStore에서의 reducer로 정의한 object의 key값으로 접근해서 받으면 된다.
const isAuth = useSelector(state => state.auth.isAuthenticated);
여기서 문제 !카운터의 couter에 접근하려면 어떻게 해야할까?
아래와 같이 접근해줘야한다. state.counter.counter에서 앞의 counter는 위의 configureStore에서의 reducer부분이고 그다음의 counter는 counter의 slice에서 정의한 initalState 부분의 변수중 하나이다.
const conunter = useSelector(state => state.counter.counter);
마지막으로 App.js 의 경로에 같이 있는 index.js 파일에 store 정보만 제공해주면 된다.
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import "./index.css";
import App from "./App";
import store from "./store/index";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Provider는 store정보를 제공하는 제공자라고 생각하면 될 것 같다.
해석하자면 App 안의 하위 컴포넌트에 모두 store 정보를 제공 하겠다. = 어디서든 접근이 가능하다 이다.
이렇게 해주면 화면에 잘 출력되는 것을 확인할 수 있다.
결과화면

Redux-Toolkit에 대한 개념이 잡혔길 바라면서 이상 포스팅을 마친다. 끝!
'React > Redux' 카테고리의 다른 글
| Redux로 전역 데이터 관리 하기 (0) | 2022.07.28 |
|---|
댓글