Redux가 몬뎅?

jh_leitmotif·2022년 3월 4일
3

Frontend 개인 공부

목록 보기
23/24

개요

React 채용 공고를 보고 있자면 Redux가 빠진 것은 보기 힘든 것 같습니다.

실제로 Redux를 사용해 본 경험이 있는데, 그 땐 그냥...

이거 쓰면 좋아요!!! 이렇게 쓰면 돼요!!!

라길래 Ctrl+C, Ctrl+V를 했을 뿐입니다.

하지만 지금의 나? 이제 이해하고 쓸 수 있을 것 같은 뿌듯한 내 자신.

언젠가 분명히 쓰게 될 저를 위해 정리해둡니다.

Redux, 그래서 뭐야 넌?

전역 관리 상태 방법 입니다.
Context API와는 다르게 Redux는 React에서만 사용되는 것은 아닙니다.

어쨌거나... React를 기준하여 풀어보면.

서비스를 설계하다보면 수 많은 컴포넌트들이 발생할 수 밖에 없습니다.
그리고 각 컴포넌트가 제어하는 state는 미친듯이 많을 겁니다. 혹시라도 Props drilling 현상이 발생해서 3 계층 이상의 자식 컴포넌트에 전달되는 것이 있기라도 한다면? 오우 쉣, 그것은 상상에 맡깁니다.

Redux는 컴포넌트에서 state가 관리되어야한다! 라는 개념을 벗어던지고, 컴포넌트 바깥 에서 관리되면 되는 거 아니야? 라는 발상에서 출발한 개념이라고 느꼈습니다. 한 곳에다가 모아놓고, 각 컴포넌트들이 원할 때만 한정해서 값을 던져주고, 갱신시켜준다면 얼마나 편하겠어요?

아니 그런데, 이 친구 사용법 좀 괴랄합니다.
Redux는 내장 함수로 action reducer dispatch를 가지고 있고 사용자는 이 세 가지 키워드에 대해 잘 알아야 어떤 흐름으로 동작하는지 알 수 있습니다.

action

맞습니다. 그 액션입니다.

쉽게 이야기해서, 어떠한 동작을 일으키겠다! 라고 선언하는 겁니다.

action은 하나의 객체로서 type이 기본적으로 포함되어야 하며 경우에 따라 전달할 값을 포함시킬 수도 있습니다. 이 때 전달하고자 하는 값의 키 이름은 기본적으로 개발자 맘대로 쓰면 됩니다. 저는 data를 즐겨 사용합니다.

export const userSession = () =>{
	return{
    	type:"USER_SESSION"
    }
}

export const userLogin = (id,pw) =>{
	return{
    	type:"USER_LOGIN",
        data:{
        	id:id,
            pw:pw,
        }
    }
}


// user_actions.js

보통 위와 같은 방식으로 action들을 한 곳에 모아두고, 필요한 컴포넌트가 import하여 사용합니다.

reducer

어라... 리듀서...? 감속기...?

Re(act State Pro)ducer => Reducer

인용 링크 : https://devlog.jwgo.kr/2018/08/23/redux-which-is-weird-term/

이 키워드에 한해서는 영어의 직역이 통하지 않습니다.

언젠가 봤던 포스트인데... React의 State를 생성해주고 변경해주는 친구! 라는 느낌으로 받아들이자며 정리되어있습니다.

말 그대로 reducer는 action의 type에 따라 그에 맞게 Store에 state를 생성해주거나 변경시켜주는 역할을 수행합니다.


export const userReducer = (state={}, action){
	switch(action.type){
    	case USER_LOGIN:
        	token = axiosRequest({url:url, data:action.data})
			return {...state, userToken : token}
        case USER_SESSION:
        	sessionStatus = axiosRequest({url:url})
            return {...state, userSession:sessionStatus}
        default:
        	return state;
    }
}

// user_reducer.js

설명하면, 전달받은 action의 type에 맞는 동작을 수행합니다.

USER_LOGIN은 보통 토큰을 발급받는데, 이에 따라 Redux Store에 userToken이라는 새로운 state를 생성하고 여기에 토큰을 집어넣습니다.

USER_SESSION은 로그인된 상태인지를 판별하는 동작이라고 가정합니다. userSession이라는 state를 생성하고 상태 여부를 업데이트합니다.

dispatch

오... 감사하게도 이 친구는 직역이 가능합니다.

action을 reducer에게 보내는 역할을 수행합니다.

import {useDispatch} from 'react-redux'
import userLogin from './user_actions'

const dispatch = useDispatch();

const goLogin = () => dispatch(userLogin);

<Loginbutton onClick={goLogin}/>

// LoginPage.js

useDispatch 훅에서 dispatch를 지원하고 있습니다.

사용법은 위와 같이, 미리 만들어둔 action 객체를 가져와 dispatch에 인자로 넘겨주기만 하면 됩니다.

React에서 Redux를 사용해보자

먼저 state들의 저장소부터 만들자.

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

const store = createStore(rootReducer);

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

Redux는 전역 상태 관리 를 해주기에 index.js에서 위와 같이 만들어 줍니다.

createStore 함수를 통해 저장소 객체를 만들어줘야하고, 인자로 어떤 action을 받아 어떻게 state를 반환할지를 알려주는 reducer를 넘겨주면 됩니다.

import { combineReducers } from 'redux';
import userReducer from './user_reducer';

const rootReducer = combineReducers({
  userReducer:userReducer
});

export default rootReducer;

// index.js
// 주의! 이 부분은 reducer 폴더에 있는 index.js입니다.
// src의 index.js가 아닙니다!!

combineReducers 라는 redux의 내장 함수는 여러 개의 reducer를 하나로 합쳐주는 기능을 가지고 있습니다. 한 곳에 모두 모여져 있을 때 유지보수에 골머리가 아픈, 그런 상황을 방지해주는 좋은 친구입니다.

우선은 현재 작성한 userReducer 하나만 넣어줍니다.

action을 정의해주자.

export const userSession = () =>{
	return{
    	type:"USER_SESSION"
    }
}

export const userLogin = (id,pw) =>{
	return{
    	type:"USER_LOGIN",
        data:{
        	id:id,
            pw:pw,
        }
    }
}


// user_actions.js

dispatch로 state를 업데이트 해보자!

import {useDispatch} from 'react-redux'
import userLogin from './user_actions'

const dispatch = useDispatch();

const goLogin = () => dispatch(userLogin);

<Loginbutton onClick={goLogin}/>

// LoginPage.js

아니 그래서, state는 어떻게 가져와?

여기서! 새로운 훅인 useSelector가 등장합니다.

import {useSelector} from 'react-redux'

const { userToken } = useSelector( state =>({
	userToken : state.userReducer.userToken,
})

return(
	<NavBar>
    	{userToken ? 
        	환영합니다!
            	:
            로그인
        }
    </NavBar>
)

// Nav.js

useSelector. 선택해주는 친구.
즉, Redux Store에서 userReducer라는 모듈이 알려주는 userToken이라는 state를 가져오겠다! 라고 하는 겁니다.

폴더의 구성

저는 이런 방식으로 관리하고 있습니다.

미리 action에 들어갈 type을 상수로 정의해두었습니다.
reducer는 index.js에서 combineReducers를 사용하며, 역할에 따라 reducer를 분기하여 관리합니다.

Redux의 장점!

Redux는 마치 하나의 창고에 물건들을 쌓아놓는 것과 비슷합니다.

즉, 같은 시점에 여러 사람들이 하나의 state를 가지고 작업할 수 있다는 뜻이 되고 동시성이 필요한 서비스에 효과적인 역할을 할 수 있습니다.

또한 Chrome 브라우저는 Redux DevTools 라는 확장 프로그램을 지원하고 있습니다.
이 프로그램을 이용해 개발자는 제어하고자하는 state가 어떤 시점에서 어떻게 변경되었는가를 관측할 수 있습니다. 이는 유지보수 뿐만 아니라, 디버깅에 큰 강점을 부여합니다.

더불어 Redux는 적재되는 값을 읽기 전용으로 제어합니다. 그러므로 값을 갱신하기 위해서는 새로운 상태를 만들어 덮어씌워주는 형태가 되어야하고, 이것은 데이터의 불변성을 제공합니다.

불변성에 대해서는 추가 포스트로 정리합니다.

Redux의 단점..

이 사진이 모든 걸 설명해주는 것 같습니다.

처음 Redux를 접했을 때의 기분은... 이루 형용할 수가 없습니다.

action은 뭔데? reducer는 뭐고 dispatch는 뭐야?
아니 일단 써보라니까 써보겠는데.. 대체 무슨 소리야?

엄격한 규칙을 따르기에, 그만큼 러닝커브가 너무 높습니다. 저는 아무리 인터넷 강의를 듣고... 이해한 사람들의 설명을 듣고... 양질의 포스팅들을 봐도 이해가 안되다가 어느 순간 띵~! 하면서 깨달았습니다.

뿐만 아니라 어찌 보면 간단한 흐름을 구현하기 위해 너무 많은 코드량을 요구합니다.

dispatch -> action -> reducer -> store -> useSelector

필수적으로 생성해야하는 폴더가 있고, 동작의 흐름이 어떻게 보면 고착화된다고 볼 수 있고, 개발자의 자유도를 제한합니다.

마무리!

Redux는 분명 많은 기업에서 쓰이고 있듯 매력적인 전역 상태 관리 방법 입니다.

하지만 항상 써야하는가? 그렇지 않다고 생각합니다.

단적인 예로, Recoil 라이브러리는 Redux와 흐름이 유사하지만 러닝커브가 매우, 매우매우, 매우매우매우 낮은 편입니다. useState를 써 본 사람이라면 그 누구나! 활용할 수 있을만큼 좋은 대체재입니다.

그러나 Recoil은 출시된지 그리 오래되지 않아 Redux에 비해 사용 빈도가 낮은 편이고, Redux DevTools와도 같은 히스토리 관리 도구가 아직 완성된 상태가 아닙니다.

https://recoiljs.org/ko/docs/guides/dev-tools/

어쨌든, 지금 만드는 서비스에 좋으면 가져다 쓰는거고! 아니면 안쓰면 되는겁니다.

profile
Define the undefined.

1개의 댓글

comment-user-thumbnail
2022년 3월 6일

공부하고 지나갑니다..

답글 달기