Redux & Redux-toolkit 사용하기

배성규·2023년 10월 1일

리액트

목록 보기
3/5

학습목표 : Redux, Redux-toolkit을 공부해보자 😎

1. 리덕스란?

상태관리 라이브러리 중 하나이고 현재 많은 곳에서 사용되고 있다.
상태관리는 UI에 맞게 데이터를 관리하거나 서버와 주고 받는 데이터를 관리하는 것을 말한다.
복잡하고 크기가 커질수록 상태관리의 난이도가 어려워지기 때문에, Props driling등의 문제를 해결하기 위해 사용된다.
아래와 같은 경우가 있을 때 사용된다.

  • 많은 state가 있고, 여러 컴포넌트에서 사용해야할 경우
  • state가 자주 업데이트되는 경우
  • state의 업데이트 로직이 복잡한 경우
  • 크기가 큰 프로젝트에서 여러 사람이 작업을 해야하는 경우

리덕스는 리액트에 종속된 라이브러리가 아니기 때문에 바닐라 자바스크립트 환경, 리액트환경 등 다양한 환경에서 사용이 가능하다.

흐름

데이터를 스토어 > 컴포넌트 > 액션 > 리듀서 > 스토어의 과정으로 변경한다.

아래 사용 방법은 리액트에서 사용하는 기준이다.

사용방법

import {Provider} from 'react-redux';
import rootReducer from './reducers';
import {createStore} from 'redux';

const store = createStore(rootReducer);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
	<Provider store = {store}>
  		<App/>
  	</Provider>
); 
  • createStore는 리듀서를 객체로 반환하여 전달한다.
  • Provider로 감싼다.
import {createStore} from 'redux';

const ADD = 'ADD';
const DELETE = 'DELETE';

export const addToDo = text => {
  return {
  	type : ADD,
    text
  }
};

export const deleteToDo = id =>{
  return {
   type : DELETE,
    id
  }
}

const reducer = (state = [], action) => {
  switch(action.type){
    case ADD:
      return [{text:action.text, id:Date.now()},...state];
    case DELETE:
      return state.filter(toDo => toDo !== action.id);
    default:
      return state;
  }
}

const store = createStore(reducer);
  • 주의점 : 리듀서에서는 리턴하는 값은 절대 mutate할 수 없다. 새로운 배열을 리턴하여준다. (불변성을 유지해야하기 때문에)
    • Spread Operator혹은 Object.assign()등 기존 state를 복사하여 사용한다.
  • 액션객체는 반드시 타입필드를 가지고 있어야한다.
  • 스토어는 하나의 프로젝트에 하나의 스토어만 가질 수 있다.
button.onclick = () => {
 	store.dispatch(ADD(1)); 
}
  • 디스패치는 액션을 발생시키는 것으로 함수가 호출되면 스토어는 리듀서 함수를 실행시켜 새로운 상태를 만든다.

mapStateToProps & mapDispatchToProps

  • components들을 store에 연결시켜준다.
function mapStateToProps(state){
 	return { toDos : state }; 
}

function mapDispatchToProps(dispatch){
 	return {
      addToDo : text => dispatch(actionCreators.addToDo(text));
    };
}
export default connect(mapStateToProps)(Home); 
  • connect는 인자로 statedispatch를 가진다.
  • 첫번째 인자는 redux store에서 온 state다.
  • 두번째 인자는 component의 props이다.
  • 최근 connect대신 사용할 수 있는 문법이 새로 생겼다.
    • useSelector()를 사용하면 리덕스의 상를 조회할 수 있다.
    • useDispatch()를 사용하면 생성한 액션을 발생시키며, 액션생성 함수를 가져온다.
import {useDispatch, useSelector} from 'react-redux';
import {plus, minus} from './counter';

export default function App() {
 const count = useSelector(state => state.counter.value);
  const dispatch = useDispatch();
  
  return (
    <div>
    	<button onClick = {() => dispatch(minus())}>-</button>
		value : {count}
		<button onClick = {() => dispatch(plus())}>+</button>
	</div>
);
}

2. Redux-toolkit

  • 리덕스 스토어 구성이 너무 복잡하다.
  • 리덕스가 유용한 작업을 수행하기 위해서는 많은 패키지가 필요하다.
  • 리덕스에는 많은 보일러플레이트가 필요하다.

다소 복잡한 리덕스의 사용을 편하게 사용할 수 있게 해주는 라이브러리이다.

npm install @reduxjs/toolkit 

리덕스 툴킷에서는 다음과 같은 API를 사용할 수 있다.

  1. configureStore() : 기존 createStore를 단순화하고, redux-thunk를 기본적으로 포함한다. 그외에도 자동으로 슬라이스 리듀서를 결합하고, Redux DevTools Extension을 사용할 수 있다.
  2. createReducer() : 기존 리듀서를 작성할 때 스위치문을 사용하고 불변성을 유지해야했지만, 해당 api는 immer를 지원하여 mutate된 값을 넣어도 자동으로 이를 인식하고 사용할 수 있게 해준다.
  3. createAction() : 주어진 액션 유형 문자열에 대한 액션 생성기 함수를 생성한다.
  4. createSlice() : 리듀서 함수의 객체, 슬라이스 이름, 초기 상태 값을 받고 해당 액션 생성자와 액션 유형을 사용하여 슬라이스 리듀서를 자동으로 생성한다.
  5. createAsyncThunk : 작업 유형 문자열과 프로미스를 반환하는 함수를 받고 해당 프로미스를 기반으로 작업 유형을 전달하는 thunk를 생성한다. (pending/fulfilled/rejected)
  6. createEntityAdapter : 저장소에서 정규화된 데이터를 관리하기 위해 재사용 가능한 리듀서 및 어댑터 세트를 생성
// store.ts
import {configureStore} from '@reduxjs/toolkit';

export const store = configureStore({
  reducer: {},
}) 
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch                                      
//index.ts
import {store} from './store'
import {Provider} from 'react-redux';

ReactDOM.render(
  <Provider store={store}>
  	<App/>
  </Provider>,document.getElementById('root')
)
// counterSlice.ts
import {createSlice} from '@reduxjs/toolkit`
import type {PayloadAction} from '@reduxjs/toolkit'

export interface CounterState {
  value : number
} 

const initialState : CounterState = {
  value : 0,
}

export const counterSlice = createSlice({
  name : 'counter',
  initialState,
  reducers:{
  	increment: (state) => {
      state.value +=1
    },
    decrement : (state) => {
      state.value -= 1
    },
    incrementByAmount:(state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
  }
})
export const {increment, decrement, incrementByAmount} = counterSlice.actions

export default counterSlice.reducer
  • immer가 적용되기때문에 mutate된 값을 넣어도 상관없음
//store.ts
import {configureStore} from '@reduxjs/toolkit'
import counterReducer from './counterSlice'

export const store = configureStore({
  reducer:{
    counter:counterReducer,
  },
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch                                   
/Counter.ts
import type{RootState} from './store'
import {useSelector, useDispatch} from 'react-redux'
import {decrement, increment} from './counterSlice'

export function Counter() {
  const count = useSelector((state: RootState) => 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>
  )
}

3. 툴킷이 어떤 것을 대체할 수 있는지?

  1. createSlice로 리듀서 함수 내부에 있는 케이스 명을 쉽게 대체할 수 있음

const initialState = {
  value : 0,
} 

export const counterSlice = createSlice({
  name : 'counter', // action 이름을 만드는데 사용되는 키 
  initialState, // 리듀서에서 사용될 state값을 정의
  reducers : { // 객체 타입. switch등으로 만들어 사용하던 케이스들을 아래와 같이 설정하면 됨 
    increment : (state, action) => {
      state.value += action.payload.data
    }
  }
})
export const counterActions = counterSlice.actions // dispatch를 위한 설정 
export default counterSlice.reducer;
  1. configureStore를 사용하여 store를 수정할 수 있다.
  • combinereducer, thunk, applyMiddleware,composeWithDevTools 모두 대체 가능
import {configureStore} from '@reduxjs/toolkit'

const store = configureStore({
  reducer:{
  }
})

export default store;

dispatch를 할때는?

기존의 dispatch는 아래와 같이 사용했음

dispatch({type : 'ADD', payload : {data});
//createSlice 내 액션값을 가져온다
dispatch(counterActions.increment({data}));
  • 더이상 action 객체를 만들어 줄 필요없이 바로 action 함수를 호출할 수 있다.
  • 매개변수로 전달된 값은 알아서 payload에 들어가짐

참고자료

profile
FE 유망주🧑‍💻

0개의 댓글