본문 바로가기
컴퓨터/React

[Redux] 리덕스의 개념 잡기/아직도 리덕스를 모른다면

by 도도새 도 2023. 8. 10.

리덕스의 개념

 

리덕스는 대표적인 상태관리 툴 중 하나이다. 사용하는 예는 이렇다. 리액트에서 컴포넌트간 데이터를 공유해야한다. 그런데 계층 구조가 복잡하여 부모의 값이 프롭을 통해 두 번 세 번 전달된다. 매우 복잡하며 중간에 프롭의 값이 어떻게 변화할지 예측이 힘들다. 이럴 때 리덕스를 사용한다.

리덕스의 핵심 가치 중 하나는 immutable, 불변이다. 자바스크립트의 모든 객체들은 기본적으로 불변하지 않다. 즉, mutable하다. 아래 코드를 보자.

const obj = { a: 1, b: 2 }
// still the same object outside, but the contents have changed
obj.b = 3

const arr = ['a', 'b']
// In the same way, we can change the contents of this array
arr.push('c')
arr[1] = 'd'

obj와 arr은 외부에서는 같은 이름, 래퍼런스를 가지지만 내부적으로는 값이 달라진다. 즉, 불변하지 않다.

불변성을 지키기 위해서는 새로운 객체를 만들어 다시 할당하는 방식을 사용하여야 한다. 이러한 방식은 예측 가능성 및 추적을 용이하게 만드는 등의 장점이있다.

리덕스의 모든 상태(State)는 불변성을 가진다.

 

리덕스의 용어

 

리덕스에서는 몇 가지 핵심 용어들이 사용된다.

 

Action

액션은 type필드를 가지는 자바 스크립트 객체이다. 어플리케이션에서 일어나는 일을 기술해 놓은 이벤트로 생각하면 된다.

type 필드는 액션을 잘 설명할 수 있는 문자열 값을 가진다.

예: “todos/TODO_ADDED”

  • 일반적으로 문자열 시작에는 카테고리 명을 적는다.
  • 일반적으로 두번째 부분(/이후)에는 실제 일어나는 이벤트명을 대문자로 적는다.

액션의 예시는 아래와 같다.

const setNameEmailAction = {
    type: 'legi/SET_NAME_EMAIL',
    name,
    email
}

 

Action Creators

액션 생성자는 액션을 생성하는 함수이다. 리턴 값으로 액션 객체를 가진다. 일반적으로 액션을 직접 작성할 필요 없도록 이 함수를 이용하게 된다.

const SET_NAME_EMAIL = 'legi/SET_NAME_EMAIL';

export interface user{
    name:string,
    email:string
}

export interface userAction extends user {
    type: any;
  }

//액션 생성 함수
export const setNameEmail = (name:string, email:string):userAction =>({
    type:SET_NAME_EMAIL,
    name,
    email})

 

Reduces

리듀서는 현재 상태(state)와 액션(action) 객체를 받는 함수이다. 경우에 따라 상태가 어떻게 변경되어야 하는지 리듀서에서 결정하며, 새로운 상태를 리턴한다.

리듀서는 받아온 액션의 타입을 기반으로 작동하는 이벤트 리스너로 생각하면 된다.

리듀서는 반드시 아래 사항을 따라야한다.

  • 리듀서는 state와 action 파라미터로 새로운 상태 값을 결정한다.
  • 존재하는 state를 변경해서는 안된다. 반드시 불변성을 지켜야한다. 즉, 이미 존재하는 상태값을 복사하고, 그 복사값을 수정한 후, 그 값을 반환하여야한다. 쉽게 말해 이전 상태의 래퍼런스와 이후 상태의 래퍼런스가 달라야한다.
  • 비동기 로직 수행, 랜덤값 계산, 사이드 이펙트 수행 등을 하지 말아야한다.(비동기 처리하기 위한 미들웨어를 추가하여 처리하여야한다.)

리듀서의 동작은 아래와 같다.

  • 들어온 액션의 타입을 보고 상태값을 변경해야하는지 확인한다.
  • 변경해야하면 변경한 새로운 상태값을, 그렇지 않으면 기존 값을 반환한다.

 

리듀서 예시

const initialValue = [{name:'', email:''}]

//리듀서
export default function legister(state = initialValue, action:userAction):user[]|undefined{
    switch(action.type){
        case SET_NAME_EMAIL:
            let result:user[]= [
                ...state,
                {
                    name:action.name,
                    email:action.email
                }
            ];
            return result;
        default:
            return state;
    }
}

state를 받아서 만약 state값이 SET_NAME_EMAIL와 일치한다면 새로운 객체 배열을 반환한다.

 

Store

스토어는 리덕스 어플리케이션의 현재 상태를 저장하는 공간이다. 스토어는 리듀서를 전달하여 생성하고 getState 메서드를 가지고 있다. getState는 메서드의 현재 상태 값을 반환 한다.

리액트에서는 리액트 리덕스의 useSelector훅을 사용하여 특정 상태 값을 가져올 수 있다.

import { createStore } from 'redux';
import rootReducer from './modules/index.ts'

const store = createStore(rootReducer)
console.log(store.getState())//상태 값을 받아온다.

현재 리덕스 팀에서는 redux가 아닌 리덕스 툴킷 사용을 권장한다. 따라서 아래 코드가 더 권장된다.

import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './modules/index.ts'

const store = configureStore({ reducer: rootReducer })

console.log(store.getState())//상태 값을 받아온다.

 

Dispatch

리덕스 스토어가 가지고 있는 메서드이다. 스테이트를 업데이트 하는 유일한 방법은 오로지 디스패치를 콜하는 방법이라야만 한다. 즉, store.dispatch()로 액션 객체를 파라미터로 넣어 그에 맞게 업데이트를 실행한다.

스토어는 dispatch가 실행되면 리듀서를 실행하고 스토어 내부에 새로운 상태값(혹은 기존 상태값)을 저장하게 된다.

store.dispatch({ type: 'counter/increment' })

Dispatch하는 작업은 이벤트를 트리거 하는 것으로 생각할 수 있다. dispatch가 실행되면 리듀서가 이벤트 리스너 처럼 작동하기 때문이다.

리액트에서 react-redux를 사용할 경우 아래 처럼 이용한다. setNameEmail을 액션 생성 함수이다.

import {useDispatch} from 'react-redux';

const dispatch = useDispatch();

dispatch(setNameEmail(name, email));

리액트 리덕트의 경우 아래처럼 각 컴포넌트에서 사용할 store를 연결시켜 줄 수 있다.

 

main.tsx

import { Provider } from 'react-redux'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <Provider store={store}>
    <App />
  </Provider>,
)

 

Selector

selector는 스토어에서 어떤 상태값을 추출할 것인지를 정해주는 함수이다. 어플리케이션과 상태값이 커지면 이 함수는 로직의 반복을 피하게 해준다.

 

const selectCounterValue = state => state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)

 

리액트에서는 앞서 잠깐 언급한 useSelector훅이 비슷한 역할을 해준다. useSelector는 컴포넌트의 특정 부분과 연결될 경우 변경시 리랜더링 기능이 추가된다.

 

import {useSelector, } from 'react-redux';

let userInfos:user[] = useSelector((state:any)=>{
        return state.legi;
    });

 

즉 스테이트에서 변경이 발생하면 위에서 userInfos를 사용하는 컴포넌트의 부분이 리랜더링 된다. state값은 불변성을 보장하도록 설계하므로 리랜더링 역시 보장한다.

 

리덕스가 동작하는 방식은 아래 이미지와 같다.

 

리덕스의 동작

 

ref

댓글