기본적인 Redux 사용방법에 대해서 알아봤다. 이제 이 Redux를 좀 더 편하게, 그리고 가볍게 사용할 수 있는 @reduxjs-toolkit
패키지에 대해 알아보고자 한다.
1. 다운로드
npm install @reduxjs/toolkit
yarn add @reduxjs/toolkit
둘 중에 편한 것으로 하자. 그리고 기존에 Redux가 설치되어있는 프로젝트라면 package.json에 들어가서 Redux를 삭제하자. 왜냐면 @reduxjs-toolkit에는 Redux의 모든 게 다 들어가 있기 때문이다.
2. 적용
이제 필요한 곳에 사용해보자. 여기서는 createSlice
와 configureStore
을 사용할 것이다. 예시는 마찬가지로 counter이다.
이 gif를 보면 증가, 5만큼 증가, 감소, counter 토글 이렇게 네 가지의 기능이 있다. 이 네 가지를 모두 해볼 것이다. CSS는 안 한다.
1. src/store/index.js
이전 포스팅의 전체 코드에서 살짝 변경되었고 새롭게 increase
와 toggleCounter
이 추가되었다.
import { createSlice, configureStore } from '@reduxjs/toolkit';
const initialState = { counter: 0, showCounter: true };
const counterSlice = createSlice({
name: "counter",
initialState: initialState,
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase(state, action) {
state.counter = state.counter + action.payload;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
},
},
});
const store = configureStore({ reducer: counterSlice.reducer });
export const counterActions = counterSlice.actions;
export default store;
1) createSlice
createSlice
는 전역 상태를 여러 조각으로 나누게 해 준다. 이 createSlice 내부에는 name과 초기 상태, 그리고 리듀서 이렇게 세 인자가 들어가는데 name은 하고싶은 대로, 상태값은 위에서 변수로 선언한 initialState
, 그리고 리듀서가 들어간다.
const counterSlice = createSlice({
name: "counter",
initialState: initialState,
reducers: {
// ...
}
});
여기 리듀서에는 어떤 액션을 했느냐에 따라서 메소드가 자동으로 호출되기 때문에 따로 액션이 필요하지 않다. 대신 서로 다른 리듀서들을 구별해놓고 각각의 리듀서에 해당하는 액션을 발생시키는 것이다.
2) reducers
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase(state, action) {
state.counter = state.counter + action.payload;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
}
}
switch문을 사용하는 것과 비슷하다. 이전 포스팅에서 Redux가 상태를 직접적으로 변경하면 절대 안 된다고 했다. 그래서 counter라는 임의의 상태에 변경된 값을 반영하는 형태로 작성했었다. 근데 여기서는 코드만 보면 state.counter++로
실제 상태에 직접 접근해서 값을 바꾸는 것처럼 보이지만 사실은 그렇지 않다. @reduxjs-toolkit 내부의 immer 패키지가 이러한 직접적 상태 변경으로 보이는 코드를 확인하면 자동으로 기존 상태를 복제한 다음 새로운 상태 객체를 생성하며 모든 상태를 변경할 수 없게 유지한다. 그리고 이렇게 변경한 상태는 변하지 않도록 오버라이딩 한다.
3) configureStore
const store = configureStore({ reducer: counterSlice.reducer });
앱의 규모가 커졌을 때 createStore에 하나의 리듀서만 전달이 되어야 하기 때문에 앱의 규모가 커진다면, 서로 다른 slice에 접근하는 리듀서가 많아지면서 문제가 생길 수 있다.
이 때에 createStore 대신에 @reduxjs-toolkit의 configureStore를 사용하면 스토어를 만드는 건 같지만 여러 개의 리듀서를 하나의 리듀서로 합칠 수 있다는 장점이 있다. 여기서는 리듀서의 값이 단일 리듀서가 될 수 있다.
위에서처럼 counterSlice.reducer
를 사용해서 모든 리듀서 메소드를 갖고 있는 counterSlice의 메소드를 가져올 수 있다.
앱의 규모가 커져서 상태 slice가 여러 개가 된다면, key값(reducer) 대신에 객체를 설정해서 그 객체 안에 원하는 대로 속성과 이름을(즉 key값을 객체로) 정하고 그 속성이 또 다른 리듀서 함수가 되게 할 수 있다. 이러한 방식으로 reducer map을 생성하는 것이다. 예시로는 { counter: counterSlice.reducer }
가 있겠다.
configureStore
가 이러한 모든 리듀서들을 하나의 큰 리듀서로 병합을 해 줄 것이다.
4) actions
export const counterActions = counterSlice.actions;
createSlice로 당연히 액션을 전달할 수도 있다. counterSlice.actions.toggleCounter
를 보자면 리턴값은 { type: "임의의 자동 생성된 고유한 식별자" }
인데, 이걸 신경 쓸 필요는 없다. 여기서 중요한 건 createSlice의 actions key
와 객체를 사용하기만 하면 된다는 것이다. export를 해줌으로써 actions와 store 모두를 export하게 된다.
2. src/components/Counter.js
우선 시작하기 전에 'react-redux' 라이브러리에서 제공하는 useSelector
로 Redux의 스토어 내부의 특정 상태를 가져오며, useDispatch
를 통해 dispatch를 한다는 점을 적는다. CSS는 Counter.module.css에 들어가 있고, 여기서는 다루지 않는다.
import classes from "./Counter.module.css";
import { useSelector, useDispatch } from "react-redux";
import { counterActions } from "../store/index";
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector((state) => state.counter);
const show = useSelector((state) => state.showCounter);
const incrementHandler = () => {
dispatch(counterActions.increment());
};
const increaseHandler = () => {
dispatch(counterActions.increase(10)); // { type: SOME_UNIQUE_IDENTIFIER, payload(Redux-toolkit이 임의로 설정함): 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={increaseHandler}>Increment by 5</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
1) return문
return문을 보면 각 버튼에 대해 onClick 이벤트로 Handler 함수들을 넣었고, 실제로 적용이 될 값은 {counter}
로 넣었다.
2) useSelector
const counter = useSelector((state) => state.counter);
const show = useSelector((state) => state.showCounter);
'react-redux' 라이브러리에서 제공하는 useSelector를 가져와서 store 내부 reducer의 counter과 showCounter 각각에 대해 접근하는 변수 두 개를 만들었다. counter 변수는 실제 값을 보여주는 용도이며 show는 이름과 같이 카운터 section의 가시성 여부를 결정한다.
3) handler 함수
const increaseHandler = () => {
dispatch(counterActions.increase(5);
};
const toggleCounterHandler = () => {
dispatch(counterAction.toggleHandler());
};
위에서 선언한 dispatch로 접근하는데, counterSlice.actions
로 정의하고 export한 counterAction
에 접근하였다.
increaseHandler
의 경우 직접 값을 선언해줘야 한다. increase의 인자로 숫자를 넣어주면 자동으로 payload로 인식이 되기 때문에 Counter.js의 increaseHandler
에서도 인자로 action.payload
를 넣어주었다. 이건 하나의 규칙과 같아서 값을 전달해주기 위해서는 payload의 형태로 리듀서에 선언이 되어있어야 한다.
다음으로 toggleCounterHandler
의 경우 그냥 toggleHandler
함수를 실행하게 되는데, 이렇게 되면 showCounter
의 값이 이전 값과는 다른 값으로 바뀌게 된다(ex. true -> false, false -> true). 토글의 형태를 지니게 되는 것이다.
4) return문
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
{show && <div className={classes.value}>{counter}</div>}
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={increaseHandler}>Increment by 5</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
각 버튼을 눌렀을 때 handler 함수가 실행되며, show의 값이 true일 때만 counter가 포함된 div가 보이도록 하였다.
이제 결과를 다시 보자. Increment를 눌렀을 때 1 증가, Increment by 5를 눌렀을 때 5 증가, Toggle Counter를 눌렀을 때 useSelector(const counter = useSelector((state) => state.counter);
)로 가져왔던 counter가 사라졌다. 이런 식으로 Redux를 사용할 수 있다.
'Web > Redux' 카테고리의 다른 글
[Redux] Redux의 기본 흐름 (0) | 2022.01.12 |
---|---|
[Redux] 리덕스 라이브러리 이해하기(+Flux) (0) | 2021.02.27 |