[React] Redux / Redux-toolkit

kim unknown·2022년 6월 23일
1

React

목록 보기
1/8
post-thumbnail

1. Redux

Redux는 앱 전체 상태를 쉽게 관리할 수 있는 라이브러리이다. Redux는 리액트만을 위한 라이브러리는 아니며, 다른 언어에서도 사용할 수 있는 라이브러리이다.

Redux를 사용하면 앱 전체 상태 관리 뿐만 아니라, 비동기 처리 상태 관리에 용이하고, 다른 개발자와 협업할 때도 용이하다.


1.1 Redux 3가지 원칙

Redux에는 3가지 원칙이 있다.

  1. Single source of truth
    프로젝트 안에서 모든 상태는 하나의 스토어 안에 하나의 객체 트리 구조로 저장된다.

  2. State is read-only
    상태는 읽기전용이다. 상태를 변화시키려면 액션을 일으켜야 한다.

  3. Changes are made with pure functions
    상태의 변화를 일으키는 reducer 함수는 순수한 함수여야 한다. 즉, 어떠한 사이드 이펙트도 만들지 않아야 한다. (reducer 함수 안에서 console을 찍는다던가 상태 변화 외에 다른 행위를 해서는 안 됨)


1.2 Redux 기본 개념

Redux에서 사용되는 개념들은 useReducer의 개념들과 매우 유사하다. useReducer에 대해 알고 있다면 쉽게 이해할 수 있을 것이다.

  1. 액션(Action)
    액션은 상태의 변경을 의미한다. 상태에 변경이 필요하면 액션을 일으켜야하며, 액션은 주로 typepayload를 가진 객체로 표현된다.

  2. 액션 생성 함수(Action Creator)
    액션 생성 함수는 액션 객체를 만들어주는 함수이다.

  3. 디스패치(Dispatch)
    액션을 Reducer로 넘겨주는 함수이다. Dispatch를 통해 액션 객체를 Reducer로 넘겨줘서 상태 변경을 일으킬 수 있다.

  4. 리듀서(Reducer)
    Dispatch로부터 액션을 넘겨 받아 새로운 상태를 만드는 함수이다. 즉, 상태 변경을 수행하는 함수이며 현재 상태와 액션 객체를 받아 새로운 상태를 리턴하는 함수이다. 따라서 (state, action) => state의 인터페이스를 따르며, 어떠한 사이드 이펙트도 있어서는 안된다.

  5. 스토어(Store)
    앱 전체의 상태를 보관하는 공간이며, 하나의 프로젝트에는 하나의 스토어만 존재한다.
    액션이 발생하여 Reducer에서 새로운 상태가 만들어질 때마다 스토어에 저장된다.

  6. 셀렉터(Selector)
    store로부터 상태 값을 가져온다.

  7. 구독(Subscribe)
    리스너 함수를 파라미터로 넣어 호출하면 상태가 업데이트될 때마다 호출된다.

Redux 작동 과정
1. dispatch를 통해 action을 reducer로 전달
2. reducer 함수는 dispatch로부터 전달 받은 action.type에 따라 이전state에서 state를 변경하여 새로운 state를 반환
3. reducer에서 새로운 state값 반환하면 store에 저장되어있는 현재 state값이 갱신


1.3 Redux 사용

스토어 생성 함수 : createStore

// redux에서 스토어를 생성하는 함수 createStore import
import { createStore } from 'redux';

// 초기 상태 정의
const initialState = {
  count: 0,
};

// reducer 함수 정의, 인자로 state와 action을 받음
// ...state로 원래의 상태를 받아온 후 변경할 부분을 변경한 후에 새로운 state를 리턴
function counter(state = initialState, action) {
  switch (action.type) {
    // action의 type이 'counter/up' 일 때
    case 'UP':
      return { ...state, count: state.count + 1 }
    // action의 type이 'counter/down' 일 때
    case 'DOWN':
      return { ...state, count: state.count - 1 }
    // default - 정의 되지 않은 다른 type이 들어왔을 때
    default:
      return state
  }
}

// createStore - 스토어 생성
// 스토어는 하나만 존재
const store = createStore(counter);

// 액션 생성 함수 정의
export const UP = () => ({
	type: 'UP'
})
export const DOWN = () => ({
	type: 'DOWN'
})

// subscribe - 스토어 상태 구독
// getState - 현재 상태 값 가져오기
// 상태가 바뀔때마다 상태 출력
store.subscribe(() => console.log(store.getState()));

// dispatch - 상태 변경, reducer로 액션을 넘겨줌
store.dispatch({ type: 'UP' });
store.dispatch({ type: 'DOWN' });

2. react-redux

앞서 말했듯이 Redux는 리액트만을 위한 라이브러리가 아니다. 때문에 리액트에서 Redux를 사용하려면 연결하는 과정이 필요하다. 이 때 사용하는 것이 react-redux이다.


2.1 Provider

Provider로 컴포넌트들을 감싸주면 하위 컴포넌트들이 Redux로 만든 store에 접근할 수 있다.
import { Provider } from 'react-redux';
<Provider store={store}>

import { createStore } from 'redux';
// react-redux로부터 Provider import
import { Provider } from 'react-redux';

// 스토어 생성
const store = createStore(reducer);

function App() {
  return (
    // 최상위에서 Provider로 컴포넌트들을 감싸주고 store를 전역적으로 사용
    <Provider store={store}>
      <하위 컴포넌트 />
    </Provider>
  )
}

2.2 UseDispatch

useDispatch를 사용해 redux의 dispatch 함수를 가져올 수 있다. dispatch로 액션을 보내면 reducer가 호출되어 해당 액션에 따라 정의된 상태로 변경된다.
import { useDispatch } from 'react-redux';
const dispatch = useDispatch()
dispatch(액션())

// react-redux로부터 useDispatch import
import { useDispatch } from 'react-redux';

export default function Counter() {
  // dispatch 함수 불러오기
  const dispatch = useDispatch();}

2.3 UseSelector

useSelector를 사용해 Redux store로부터 상태를 가져올 수 있다. selector는 상태 값을 읽을 수만 있으며, 상태를 변경해서는 안된다.

// react-redux로부터 useSelector import
import { useSelector } from 'react-redux';

export default function Counter() {
  // store로부터 state값 불러오기
  const count = useSelector((state) => state.count);}

3. Redux-Toolkit

Redux만을 사용해서 개발할 수도 있지만, Redux Toolkit을 사용하면 훨씬 편리하게 상태 관리 기술 개발을 할 수 있다. 또한 기존의 많은 보일러 플레이트를 제거할 수 있다는 것도 장점이다.


3.1 설치

redux, react-redux, redux toolkit 설치
npm install redux react-redux @reduxjs/toolkit


3.2 configureStore

앞서 Redux의 createStore를 살펴보긴 했지만, 사실 더이상 createStore는 지원이 되지 않아 사용이 권장되지 않는다. 그래서 createStore를 최신 버전 Redux에서 사용하면 취소선이 그어지는 것을 볼 수 있다.
(→ Redux의 버전을 낮추면 임시방편으로 해결이 되긴 한다.)

무튼 createStore 대신 redux toolkit의 configureStore를 사용할 수 있다. redux-thunk와 DevTools도 제공해주기 때문에 따로 설정해줄 필요가 없다.

import { configureStore } from '@reduxjs/toolkit';
export default configureStore({ reducer: { }, })

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({
  reducer: { // reducer들을 정의
  	count: countReducer,
    num: numReducer,},
})

export default store;

3.3 createAction

기존의 액션 생성 함수는 다음과 같이 액션에 대해 일일이 만들어줘야하는 번거로움이 있었다.

function UP(amount) {
  return {
    type: 'UP',
    payload: amount,
  }
}const action = UP(3)

createAction은 액션 생성 함수를 만드는 함수이다. createAction을 사용하면 직접 객체를 만들 필요가 없기 때문에 훨씬 간단하다.

import { createAction } from "@reduxjs/toolkit";
const 액션생성함수명 = createAction('액션타입')

import { createAction } from '@reduxjs/toolkit'

const UP = createAction('UP')

const action = UP(3)

3.4 createReducer

기존의 Reducer는 ...state로 원래 state를 복사해 온 후 상태를 변경하여 새로운 state를 반환하는 형태 였다. 이렇게 해야만 참조 값이 바뀌므로 변화를 감지하고 렌더링이 되기 때문이다.

switch (action.type) {
  case 'UP':
    return { ...state, count: state.count + 1 }default:
    return state
}

하지만 createReducer를 사용하면 state를 복사하고 새로운 state를 리턴해줄 필요가 없다. 변경되는 부분만 변경해주면 되므로 코드가 매우 간단해진다.

import {createReducer} from '@reduxjs/toolkit';
createReducer(initialState, {여러 reducer를 객체 형태로 묶음})

import {createReducer} from '@reduxjs/toolkit'

const counterReducer = createReducer(0, (builder) => {
  builder.addCase(increment, (state, action) => state + action.payload)
  builder.addCase(decrement, (state, action) => state - action.payload)
})

3.5 createSlice

Slice는 여러 Redux 구현체를 하나의 객체로 모은 것이며, createSlice를 이용하여 보일러 플레이트를 없애고 쉽게 action creator, reducer를 만들 수 있다.

createSlice는 내부적으로 createAction, createReducer 함수가 사용된다. createSlice에 선언된 slice 이름을 따라서 reducer와 그리고 그것에 상응하는 액션 생성자와 액션 타입을 자동으로 생성한다. 때문에 createSlice를 사용하면 따로 createAction, createReducer를 작성할 필요가 없다.

slice 객체는 name, initialState, reducers를 구성요소로 갖는다.

  • name : slice 이름
  • initailState : 초기 상태
  • reducers : 사용할 reducer들을 정의

import {createReducer} from '@reduxjs/toolkit';
createSlice({name: slice명, initialState, reducers: { } })

⁕ 매번 ...state와 return 할 필요 없어졌음

import {createReducer} from '@reduxjs/toolkit'

const initialState = {
  count: 0
};

const countSlice = createSlice({
  name: "counterReducer",
  initialState,
  reducers: {
    UP: (state, action) => {
      state.count = state.count + 1
    },
    DOWN: (state, action) => {
      state.count = state.count - 1
    },
  },
});

export default countSlice.reducer;

3.6 createSelector

createSelector는 Redux 스토어에서 상태 데이터를 가져오는 함수이다. useSelector를 사용하면 되는 데 굳이 createSelector를 사용해야하나 라는 의문이 들 수도 있다. 하지만, useSelector는 약간이 단점이 있다.

useSelector는 실행될 때마다 매번 새로운 배열을 반환하게 되면서 이전에 참조하고 있던 객체 주소가 현재 주소와의 차이를 발생시킨다. 그리고는 재렌더링을 발생시키는데 이것이 성능 문제로 이어질 수도 있다는 단점이 있다. 때문에 createSelector를 사용하면 useSelector의 단점을 보완할 수 있다.

import { createSelector } from '@reduxjs/toolkit';

const countSelector = state => state.count
const numSelector = state => state.num

const CountNumSelector = createSelector(
  // 셀렉터들 정의
  countSelector, 
  numSelector,
  (count, num) =>
  	// 리턴할 데이터)

참고
Redux
리덕스
redux-toolkit 사용법
Redux Toolkit (리덕스 툴킷)은 정말 천덕꾸러기일까?

profile
과거의 나에게 묻기 위한 기록

0개의 댓글