redux-toolkit 공식 문서 튜토리얼 내용을 개인 학습용으로 정리한 글입니다.
Redux Toolkit은 Redux를 사용하기 쉽게 만들기 위해 Redux 팀에서 공식으로 제공하는 개발도구 입니다. Redux Toolkit은 Redux에 대한 세 가지 일반적인 문제를 해결하기 위해 만들어졌습니다.
모든 사례를 해결할 수는 없지만 create-react-app
및 apollo-boost
의 정신에 따라, Redux Toolkit은 설정 프로세스를 추상화하고, 가장 일반적인 사용 사례를 처리하는 몇 가지 도구를 제공하고, 사용자가 어플리케이션 코드를 단순화 할 수있는 몇 가지 유용한 유틸리티를 포함하고 있습니다.
리덕스는 기본적으로 액션 타입, 액션 생성함수, 리듀서를 정의하는 코드를 작성해야 하기 때문에 보일러플레이트 코드를 많이 준비할 수밖에 없다. 이 과정에서 코드 단순화를 위해 redux-actions을, typescript 지원을 위해 typesafe-actions, 불변성 간소화 코드 작성을 위해 immer 등등의 라이브러리를 별도로 도입해야 했다. 리덕스 툴킷은 이런 문제를 해결하기 위해 리덕스 개발팀에서 공식적으로 제공하는 라이브러리이다. 기본적으로 리듀서, 액션타입, 액션 생성함수, 초기상태를 하나의 함수로 선언할 수 있다. 이 4가지를 통틀어서 slice 라고 정의한다.
Simple
스토어 설정, 리듀서 생성, 변경 불가능한 업데이트 로직 등과 같은 일반적인 사용 사례를 단순화하는 유틸리티 포함
Opinionated: 스토어 설정을 위한 좋은 기본값 제공, 가장 일반적으로 사용되는 Redux addons 내장
Powerful: Immer 및 Autodux와 같은 라이브러리에서 영감을 얻어 mutative하게 작성해도 불변성 로직으로 작성 가능, 전체 상태 "슬라이스"를 자동으로 생성 가능
Effective: 적은 코드로 많은 작업 수행 가능
Create React App 사용(권장 방식)
npx create-react-app my-app --template redux
or cra-template-redux-typescript
npx create-react-app my-app --template redux-typescript
기존 앱에 추가
npm install @reduxjs/toolkit
or
yarn add @reduxjs/toolkit
Redux Toolkit에는 다음 API가 포함됩니다.
configureStore()
createStore
를 단순화된 구성 옵션과 좋은 기본값을 제공하기 위해 래핑합니다. 슬라이스 리듀서를 자동으로 결합하고, 제공하는 Redux 미들웨어를 추가하고, redux-thunk
를 기본적으로 포함하며, Redux DevTools Extension을 사용할 수 있습니다.createReducer()
createAction()
createSlice()
createAsyncThunk
createEntityAdapter
configureStore
를 사용하여 Redux 스토어 생성configureStore
는reducer
함수를 명명된 인수로 받습니다.configureStore
는 좋은 기본 설정으로 store를 자동으로 설정합니다.Ex. app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
// 1. Create a Redux Store
// 자동으로 Redux DevTools extension 설정
export const store = configureStore({
reducer: {},
});
<Provider>
컴포넌트로<App />
를 감싸줍니다. <Provider store={store}>
Ex. index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { store } from './app/store';
import { Provider } from 'react-redux';
import * as serviceWorker from './serviceWorker';
// 2. Provide the Redux Store to React
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
createSlice
createSlice
를 불러와 문자열 이름, 초기 상태 및 명명된 리듀서 함수와 함께 사용합니다. Ex. counterSlice.js
import { createSlice } from '@reduxjs/toolkit'
// 3. Create a Redux State Slice 리덕스 상태 슬라이스 생성
// 슬라이스를 만들려면 슬라이스를 식별하는 'name: 문자열 이름', 'initialState: 초기 상태값', '상태 업데이트 방법을 정의하는 하나 이상의 reducer 함수'가 필요
// 슬라이스가 생성되면 생성된 redux 액션 생성자와, 전체 슬라이스에 대한 reducer 함수를 내보낼 수 있다.
// Redux Toolkit createSlice과 createReducerAPI는
// Immer를 내부에서 사용하여 변경 불가능한 올바른 업데이트가 되는 "불변" 업데이트 로직을 작성
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
},
},
})
// 각 케이스 리듀서 함수에 대해 액션 생성자 생성
export const { increment, decrement, incrementByAmount } = counterSlice.actions
// coutnerSlice의 reducer를 내보냄(default로 설정해서 다른 이름으로 받을수 o)
export default counterSlice.reducer
다음으로 counterSlice 에서 리듀서 함수를 가져 와서 스토어에 추가해야 합니다. 리듀서 매개 변수 내부에 필드를 정의하여 해당 상태에 대한 모든 업데이트를 처리하기 위해 이 슬라이스 리듀서 함수를 사용하도록 스토어에 지시합니다.
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'
export default configureStore({
reducer: {
counter: counterReducer,
},
})
이제 React-Redux 훅을 사용하여 React 컴포넌트가 Redux 스토어와 상호 작용하도록 할 수 있습니다.
useSelector
훅을 사용하여 스토어에서 데이터 읽기dispatch
함수를 useDispatch
훅에서 가져오고 필요에 따라 액션을 전달합니다. import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'
export function Counter() {
const count = useSelector((state) => state.counter.value)
const dispatch = useDispatch()
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
)
}
// increment/decrement 버튼 클릭할 때마다,
// 해당하는 redux action이 스토어로 발송(dispatch)
// counter slice reducer는 actions를 보고 상태 업데이트
// <Counter> 컴포넌트는 스토어로부터 전달된 새로운 상태값을 보고, 새로운 데이터 자체를 리렌더링