책 읽다가 코딩하다 죽을래

[React] 상태관리 라이브러리 Redux 본문

코딩/react

[React] 상태관리 라이브러리 Redux

ABlue 2021. 7. 30. 09:18

Redux는 전역 상태 관리를 편히 사용할 수 있게 해주는 라이브러리입니다.

 

 

목차

1. React에서 Redux를 사용하는 이유

2. Redux 구조

3. Redux 사용법

 

 


1. React에서 Redux를 사용하는 이유


더보기

에서 컴포넌트 내에서의 데이터는 state로 해결된다.

그럼 컴포넌트 간에서는? props가 있다.

그렇지만 되게 제약사항이 많다.

props는 양방향이 아닌 일방향이다.

부모가 자식에게 props를 줄 수 있지만

부모에게 받은 props를 수정할 수 없을뿐더러

애초에 부모에게 데이터를 줄 수 있는 수단이 없다.

 

게다가 만약 컴포넌트 관계가 A->B->C->D와 같고

A의 데이터를 D에게 전달하려면

B와 C를 거쳐야 하고 또한 데이터 변화가 없는 B와 C도 필요 없는 리랜더링 일어난다.

 

이렇게 자식이 부모에게 데이터를 줘야 할 상황이나 

데이터 변화가 있는 컴포넌트만 랜더링이 일어나게 만들려면

Redux를 사용해야 한다.

Redux가 무엇이냐면은 React안의 전역 변수라고 생각하면 된다.

 

 


2. Redux 구조


더보기
Redux 구조

Redux의 구조는 다음과 같다

Redux는 스토어, 리듀서, 액션, 디스패치, 구독이 있는데 각각 설명하자면

 

 

스토어(store) : 전역 변수들이 담겨있는 곳이다.

사실상 이해하기 쉽게 전역 변수라고 표현했지만 모든 컴포넌트에서 접근할 수 있는 변수 가 더 정확한 표현이다.

 

액션 (action) : 사용자가 웹사이트에 들어와서 할 수 있는 모든 행동들을 추상화한 것이다.

또한 액션을 통해서 다른 컴포넌트들에게도 데이터가 변경되었다는 것을 알려주기 위한 통보이기도 하다.

예로 들면 회원가입 폼에서 회원정보를 입력하고 가입 버튼을 누르는 과정에서 액션은 '가입하기'가 있다.

 

 

디스패치 (dispatch) : 사용자가 액션을 취함으로써 웹사이트 내에서 실행되는 로직이다.

예로 들면 사용자가 가입하기를 누르면 회원이 입력한 정보를 서버에 저장하는 로직이 dispatch(회원정보 저장()); 로 표현된다.

 

리듀서 (reducer) : 사용자의 액션과 액션을 통해 동작해야 하는 로직들이 기술되어 있는 곳이다.

보통 액션과 액션 크리에이터, 초깃값, reducer가 있고 reducer는 switch구문으로 이루어져 있다.

 

 

지금 코드가 무엇을 의미하는지는 중요하지 않아요! 코드의 형태만 보세요!

 

 

액션

 

액션 크리에이터

 

 

리듀서

 

리듀서의 내용을 보면 공통적으로 ...state가 있는데 이 부분은

리듀서는 불변성을 유지하기 위함이다.

 

Redux는 어찌 보면 전역 변수이다.

소프트웨어 공학을 배운 사람이면 알겠지만 전역 변수는

설계하는 데에 있어 치명적이라는 것을 알 수 있다.

전역 변수는 모듈 간 결합도 중 위에서 2번째인 공통 결합도이니

Redux를 쓰면 컴포넌트 간의 결합도가 매우 높아지는데

이를 막기 위해 자신이 바꾸는 데이터를 제외한 나머지 데이터는 바뀌지 말라는 뜻으로 ...state를 사용함으로써

리듀서를 순수한 함수(동일한 입력값엔 동일한 반환 값이 나오는 함수)로 만드는 것이다.

또한 바꾸는 그 과정도 action과 action 크리에이터라는 절차를 따르지 않고는 바뀔 수 없다는 무결성도 안 보이게 지켜져 있는 것이다.

 

 

그리고 마지막으로

 

구독 : 리듀서를 통해 바뀌어진 데이터를 바라보는 컴포넌트들을 리랜더링 시켜주는 과정이다.

데이터를 바라보지 않는 컴포넌트들은 아무 변화가 없다.

 

 

React를 처음 공부하는 사람이 제일 힘들어하는 구간이기도 하다.

특히 dispatch는 비동기처리이다 보니 비동기에 잘 모르시는 분들은 제어하기 힘든 부분도 있을 것이다.

 

 

 


3. Redux 사용법


더보기

 

1. 해당 프로젝트 터미널에 Redux 패키지를 설치한다.

 

npm i redux react-redux
yarn add redux react-redux

 

 

폴더구조

 

  

redux라는 폴더를 따로 만든 다음 그 안에 이런 식으로 파일과 폴더를 만들어준다.

 

 

2. configStore.js 에서 먼저 store를 정의해준다.

 

import { createStore, combineReducers } from "redux";
import bucket from './modules/user';
import { createBrowserHistory } from "history";
// 브라우저 히스토리를 만들어준다.
export const history = createBrowserHistory();
// root 리듀서를 만들어준다.
// 나중에 리듀서를 여러개 만들게 되면 여기에 하나씩 추가한다.
const rootReducer = combineReducers({ user });
// 스토어를 만듭니다.
const store = createStore(rootReducer);
export default store;

 

 

3. index.js에 store를 주입한다.

 

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {BrowserRouter} from 'react-router-dom';
// 우리의 리듀서를 리덕스를 주입해줄 프로바이더를 불러온다.
import { Provider } from "react-redux";
// 연결할 스토어도 가지고 온다.
import store from "./redux/configStore";
ReactDOM.render( 
	<Provider store={store}>
    	<BrowserRouter> 
    	    <App />
        </BrowserRouter>
    </Provider>,
	document.getElementById("root") 
);

 

 

4.  user.js에 액션, 액션크리에이터, 리듀서를 정의한다.

 

 

// actions
const LOG_IN = "LOG_IN";
const LOG_OUT = "LOG_OUT";

// action creators
const logIn = (user) => {
	return { type: LOG_IN, user }
}
const logOut = (user) => {
	return { type: LOG_OUT }
}
// initialState
const initialState = {
	user: null,
    is_login: false,
}

// reducer
export default function reducer(state = initialState, action = {}){ 
	switch(action.type){
    	case "LOG_IN" : {
        	return {...state, user: action.user, is_login: true};
        }
        case "LOG_OUT" : {
        	return {...state, user: null, is_login : false};
        }
    default: return state;
	}
}


// action creator export
const actionCreators = {
	logIn,
    logOut,
};

export { actionCreators };

 

만약 user의 로그인과 로그아웃의 액션을 비유하자면 다음과 같다. 

 

action은 보통 대문자 상수로 이루어지며 대문자 string형의 데이터를 가지고 있다.

action creator 변경되는 데이터 값을 입력 인자로 넣고 

{ type : 액션, 변경되는 데이터 값 } 을 반환해준다.

 

initialState는 store에 들어있는 데이터들의 초깃값이다.

initialState는 reducer에 첫번째 입력 인자에 들어간다.

 

reducer는 switch 문으로 이루어져있으며

type을 매개 인수로 각 액션에 대해 취해야 할 로직이 기술되어 있다.

각 case 문들은 기본적으로 return { ...state } 로 이루어져 있는데

자신이 바꿀 데이터를 제외한 다른 데이터는 변하지 말라는 불변성을 유지하기 위함이다.

또한 return 전에 다른 코드를 집어넣어 세세한 동작을 구현해도 된다.

 

 

 

 

import React from 'react'
import { useSelector, useDispatch } from "react-redux";
import { actionCreators as userAction} from './redux/module/user';

const LoginPage = (props) => {
	const dispatch = useDispatch();
    const is_login = useSelector(state => state.user.is_login); 
    const user = useSelector(state => state.user.user); 
    return (
        <div>
        	<p>나는 누구인가? {user}</p>
            <p>로그인을 했는가? {is_login}</p>
            <button onClick={() => dispatch(userAction.logIn("ablue"))}>로그인하기</button>
            <button onClick={() => dispatch(userAction.logOut())}>로그아웃하기</button>
        </div>
    )
}

export default LoginPage

 

useSelector는 store에 있는 변수를 갖고오는 함수이다.

const 변수명 = useSelector(state => state.모듈명.변수명);

이렇게 구성되어있으며 제가 만든 코드는

user.js 에 있는 is_login라는 state를 갖고 오는 것이다.

 

redux에서 갖고온 state들은 다른 변수에다 저장해 초기화하는 것은 가능하지만

state 자체로는 dispatch를 통해서 초기화해야한다.

즉 action을 통해서 정상적인 절차를 거쳐야지만 값을 초기화할 수 있다는 것이다. 

 

dispatch는 redux에 액션 함수를 불러내는 것이다.

dispatch(액션 크리에이터()) 이런 식으로 불러내며

dispatch는 비동기이다.

 

이런 식으로 이제 어떤 컴포넌트에서든 공용으로 저장할 수 있는 전역 변수가 생긴 셈이다.

 

그리고 Redux의 상태 값들은 새로고침(F5)을 하면 모두 사라짐을 유의하자.