Redux과제를 끝내고 Redux toolkit을 사용하면 더 쉽게 코드를 작성할 수 있다하여 공부해 보았다. Udemy 강의를 보면서 그대로 따라친 코드이지만 앞으로 만들어볼 프로젝트에서 참고할 일이 있기를 바라며 블로그를 작성해본다.
이 어플리케이션은 사용자가 로그인 여부에 따라 보여지는 화면을 리덕스를 이용해 다르게 구현하였다.
사용자의 로그인 여부는 어플리케이션 전체에서 공유되는 전역 상태이기 때문에, 이를 컴포넌트 간에 porps로 전달하거나 컴포넌트 상태로 관리하는 것은 비효율적이다. 이를 리덕스로 관리하면 전역 상태에서 일관된 방식으로 사용할 수 있으며, 로그인 여부와 관련된 로직이 변경될 경우에도 코드 수정이 간편해진다.
🔗설치방법과 포함된 API들(공식문서)
1. Store
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counter';
import authReducer from './auth'
const store = configureStore({
reducer: {counter: counterReducer, auth:authReducer}
});
export default store;
👇🏻자세한 구현사항
1. configureStore는 createStore처럼 store를 만들고 여러개의 리듀서를 하나의 리듀서로 쉽게 합칠 수 있다.
import {configureStore} from 'redux';
2. 리듀서 맵을 만들어준다.
slice가 여러개라 하더라도 리덕스 스토어는 하나밖에 없다. 아래의 코드는 여러개의 리듀서를 하나의 스토어에 등록하는 방법이다. reducer: 는 함수뿐만 아니라 리듀서 맵 역할을 하는 객체를 인자를 받기도 한다.
const store = configureStore({
reducer: {counter: counterReducer, auth:authReducer}
});
2.Reducer, createSlice
import { createSlice } from '@reduxjs/toolkit';
const initialAuthState = {
isAuthenticated:false,
}
const authSlice = createSlice({
name :'authentication',
initialState:initialAuthState,
reducers:{
login(state){
state.isAuthenticated = true;
},
logout(state){
state.isAuthenticated = false;
},
}
})
export const authActions = authSlice.actions;
export default authSlice.reducer;
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 counterActions = counterSlice.actions;
export default counterSlice.reducer;
👇🏻자세한 구현사항(counterSlice)
1. createSlice를 import한다.
import { createSlice } from '@reduxjs/toolkit';
2. counterSlice는 서로 다른 리듀서에 해당하는 고유액션 식별자를 자동으로 생성한다.
name: 아무 문자열이나 가능하다.
initialState: 초기상태를 설정해준다.
reducers: 메서드를 작성해준다.
- 모든 메서드들은 리덕스에 의해 호출되고 현재 상태를 받는다.
- 여기서는 어떤 액션을 했느냐에 따라 메서드가 자동호출되어서 액션이 필요가 없다. => if, switch문이 불필요하다.
- 상태를 직접적으로 수정하는것처럼 보이지만 redux toolkit은 내부적으로 immer라는 패키지가 자동으로 원래 상태를 복제한다.
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;
},
}
})
3. 리덕스 슬라이스에서 action creator와 reducer를 추출한다.
- counterSlice.actions는 createSlice() 함수로 생성한 리덕스 슬라이스에서 생성된 action creator들을 담고 있는 객체를 의미한다. 이 객체는 액션 타입과 액션 생성 함수를 모두 포함하고 있으며, dispatch() 함수로 액션을 보낼 때 사용된다.
- counterSlice.reducer는 createSlice() 함수로 생성한 리덕스 슬라이스에서 생성된 reducer 함수를 의미한다. 이 함수는 현재 상태와 액션 객체를 인자로 받아 새로운 상태를 반환한다.
export const counterActions = counterSlice.actions;
export default counterSlice.reducer;
3. Dispatch
import classes from './Counter.module.css';
import { useSelector, useDispatch } from 'react-redux';
import { counterActions} from '../store/counter';
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector(state => state.counter.counter)
const show = useSelector(state => state.counter.showCounter)
const incrementHandler = () =>{
dispatch(counterActions.increment())
}
const increseHandler = ()=>{
dispatch(counterActions.increase(10))
}
const decrementHandler = () =>{
dispatch(counterActions.decrement())
}
const toggleCounterHandler = () => {
dispatch(counterActions.toggleCounter())
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
{show && <div className={classes.value}>{counter}</div>}
<div>
<button onClick={incrementHandler}>increment</button>
<button onClick={increseHandler}>increseby5</button>
<button onClick={decrementHandler}>decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
👇🏻자세한 구현사항(counterSlice)
1. createSlice()로 생성한 액션과 액션 생성 함수를 포함하고 있는 객체인 counterActions를 import해준다.
import { counterActions} from '../store/counter';
2. useSelector로 필요한 상태 부분을 가져온다.
state인자를 통해 전체 상태 객체에 접근하고 첫번째 counter 인자를 통해 counter 리듀서가 관리하는 상태 slice에 접근하고, 해당 slice에서 counter 프로퍼티 이름을 가진 값을 추출하여 반환한다. 두번째 show 변수도 같은 원리로 작동한다.
// store/counter.js
const initialCounterState = {counter:0, // <= 두번째 counter
showCounter: true}
const counterSlice = createSlice({
name: 'counter', <= 첫번째 counter
...생략
)
const counter = useSelector(state => state.counter.counter)
const show = useSelector(state => state.counter.showCounter)
3. import한 counterAction인자를 dispatch한다.
incrementHandler 함수는 counterActions 객체에서 increment 액션을 가져와 dispatch하는 함수이다.
이때 increment 액션은 createSlice함수에서 자동으로 생성된 액션 객체이며
{type: 'increment'}
와 같은 형태를 가지고 있다.
이 함수를 호출하게 되면 increment 액션이 리덕스 스토어에 전달되며 리덕스 스토어에서 counter 리듀서가 처리하게 된다.
그렇다면 payload가 필요한 increasHandler 함수는 어떻게 해야할까?
dispatch(counterActions.increase(10))
이렇게 작성해주면
{type: 'increase', payload:10}
와 같은 형태로 만들어진다.
위의 increment와 같이 increase 액션과 함께 지정된 payload 값이 리덕스 스토어에 전달되고 리덕스 스토에서 counter 리듀서가 처리하게 된다.
App.js
import { Fragment } from 'react';
import { useSelector } from 'react-redux';
import Counter from './components/Counter';
import Header from './components/Header'
import Auth from './components/Auth'
import UserProfile from './components/UserProfile';
function App() {
const isAuth = useSelector(state => state.auth.isAuthenticated)
return (
<>
<Header/>
{!isAuth && <Auth/>}
{isAuth && <UserProfile/>}
<Counter />
</>
);
}
export default App;
마무리
props로 상태전달 => 리덕스 사용 => 리덕스 툴킷사용 순서로 코드가 효율적으로 바뀌는 상황을 볼 수 있었다. 이렇게 연습해본 툴킷을 바탕으로 어제 했던 과제를 리팩토링 해보고 싶었지만 수량조절 부분에서 장렬히 실패하고 말았다. ㅠㅠ 원래 있던 코드를 리팩토링하는 것은 얼마나 어려운일인가.. 하는 큰 깨달음을 얻었다. ^^ 툴킷 사용으로 인해 자동으로 진행되는 부분이 있어 아직도 조금 헷갈리지만 역시 계속 사용하면서 익숙해지는 방법밖에 없는것 같다.
'React' 카테고리의 다른 글
[React] What is React? (2) | 2023.05.07 |
---|---|
[Redux] Redux의 데이터 흐름과 Flux패턴 (3) | 2023.04.24 |
[React] 쇼핑몰앱 장바구니기능을 구현해보자 (2) | 2023.04.21 |
[React]검색어 자동완성과 클릭투에디트를 구현해보자 (1) | 2023.04.20 |
리액트로 모달창,토글,탭,태그 구현 (0) | 2023.04.19 |