리덕스를 좀 더 쉽게 사용하기 위해 만들어 진 것이 리덕스 툴킷이다.
주요 차이점은 다음과 같다.
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";
export const store = configureStore({
reducer: {
counterStore: counterReducer,
},
});
기능별로 리듀서를 나누어 store를 생성할 수 있다.
기존의 redux에서는 combineReducers함수를 이용해서 설정해야 했었는데 간편해졌다.
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { store } from "./app/store";
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
react-redux에서 지원하는 Provider 컴포넌트를 최상위 컴포넌트에 위치시켜서 앱 내에서 store가 공유되게 한다.
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
count: 0,
};
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.count += 1;
},
decrement: (state) => {
state.count -= 1;
},
reset: (state) => {
state.count = 0;
},
incrementByAmount: (state, action) => {
state.count += action.payload;
},
},
});
export const { increment, decrement, reset, incrementByAmount } =
counterSlice.actions;
export default counterSlice.reducer;
상태와 액션 타입과 리듀서를 합친 모양새다.
슬라이스는 저장소의 일부로 보면 될 것 같다.
해당 슬라이스의 이름은 "counter" 로 설정했고 prefix로 이용된다고 한다.
해당 슬라이스의 초기값은 initialState로 설정된다.
reducers 속성은 필수 속성인데, 복수형이라는 것에 주의하자.
기존의 리듀서에서 액션타입에 따른 if else구문 또는 switch구문 대신에 각각의 액션 타입에 대한 로직을 함수별로 분리했다.
dispatch 함수에 액션을 넣어주는 대신 reducers 객체내에 있는 함수와 같은 이름의 함수를 호출값을 매개변수로 전달한다.
예: dispatch({type:"ADD", payload})
대신 dispatch(increment())
reducers 객체내의 함수와 같은 이름의 함수는 슬라이스의 actions 속성에 나열되어 있다.
슬라이스의 reducer 속성은 해당 슬라이스 내부의 reducers를 하나로 통합한 것이다.
이는 store를 만드는 configureStore에서 사용된다.
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement, reset, incrementByAmount } from "./counterSlice";
import { useState } from "react";
const Counter = () => {
const count = useSelector((state) => state.counterStore.count);
const dispatch = useDispatch();
const [incrementAmount, setIncrementAmount] = useState(0);
const addValue = Number(incrementAmount) || 0;
const resetAll = () => {
setIncrementAmount(0);
dispatch(reset());
};
return (
<section>
<p>{count}</p>
<div>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
<input
type="text"
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<div>
<button onClick={() => dispatch(incrementByAmount(addValue))}>
Add Amount
</button>
<button onClick={() => resetAll()}>reset</button>
</div>
</section>
);
};
export default Counter;
store 내부의 값을 가져올 때는 store를 정의할때 사용한 reducer내부의 키값을 이용해 접근한다.
dispatch를 할 때는 해당 슬라이스의 리듀서 함수를 호출한다.
또한 리듀서 함수의 매개변수에 값을 전달하면 이 값은 리듀서 함수 내부에 action.payload
와 매핑된다.
import Counter from "./features/counter/Counter";
function App() {
return (
<main className="App">
<Counter />
</main>
);
}
export default App;