본문 바로가기
컴퓨터 사이언스 Computer Science/프론트엔드 Frontend

[프론트엔드] 상태관리 (2) - Redux (상태관리 라이브러리)

by 이땡칠 2022. 6. 10.

Redux 

 

React에서 전역 상태관리의 필요성 

  • React에서 State는 component 안에서 관리됩니다. 
  • 자식 컴포넌트들 간의 다이렉트 데이터 전달은 불가능 합니다. 
  • 자식 컴포넌트들 간의 데이터를 주고 받을 때는 상태를 관리하는 부모 컴포넌트를 통해서 주고 받습니다.
  • 그런데 자식이 많아진다면 상태 관리가 매우 복잡해집니다.
  • 상태를 관리하는 상위 컴포넌트에서 계속 내려 받아야합니다.  => Props drilling 이슈 발생

 

Redux 를 언제 사용해야 할까? (공식문서 내용 번역)

https://redux.js.org/faq/general#when-should-i-use-redux

  • 어플리케이션의 많은 공간에서 필요한 상당한 양의 데이터가 있을 때
  • 상태값이 매우 빈번하게 업데이트될 때
  • 상태를 업데이트하는 로직이 매우 복잡할 때
  • 어플리케이션이 중간 또는 큰 크기의 코드베이스를 가지고 있고, 많은 사람들이 작업중일 때 
  • 시간에 따라 상태가 어떻게 변화하는지 확인해야할 때 

 

 

Redux 

  • 자바스크립트 상태관리 라이브러리입니다. 
  • Redux는 일관적으로 동작하고, 서로 다른 환경(서버, 클라이언트, 네이티브)에서 작동하고, 테스트하기 쉬운 앱을 작성하도록 도와줍니다.
  • 리덕스를 사용하면 컴포넌트들의 상태 관련 로직들을 다른 파일들로 분리시켜서 더욱 효율적으로 관리 할 수 있으며 글로벌 상태 관리도 손쉽게 할 수 있습니다.

 

(덧붙임. 공식문서 번역한 내용)

  • 자바스크립트 객체/배열은 mutable 하다. 객체나 배열의 속성값을 변경할 때, 메모리에서 참조하는 객체/배열은 같으며, 내부값만 바뀐다.
  • 만약 불변성을 유지하며 수정하고 싶다면, 객체/배열을 복사하여 그 복사한 객체/배열의 내부값을 변경해야 한다. 
  • 객체/배열의 복사는 스프래드 연산자를 사용하여 할 수 있다. 
  • 리덕스는 모든 상태 업데이트가 불변성을 유지하면서 수행되는 것을 기대한다. 

 

Redux 주요 키워드

1. 액션 (Action)

상태에 변화가 필요할 때 발생시킴 (객체하나로 표현)
type을 필수로 그외의 값들은 개발자 마음대로 생성

 

2. 액션 생성함수 (Action Creator)

액션을 만드는 함수로 파라미터를 받아서 액션 객체 형태로 만들어줍니다.

 

3. 미들웨어 

미들웨어를 사용하면 액션 객체가 리듀서에서 처리되기 전에 우리가 원하는 작업들을 수행 할 수 있습니다. 

미들웨어는 주로 비동기 작업을 처리 할 때 많이 사용됩니다.

 

예) 

  • 특정 조건에 따라 액션이 무시되게 만들 수 있습니다.
  • 액션을 콘솔에 출력하거나, 서버쪽에 로깅을 할 수 있습니다.
  • 액션이 디스패치 됐을 때 이를 수정해서 리듀서에게 전달되도록 할 수 있습니다.
  • 특정 액션이 발생했을 때 이에 기반하여 다른 액션이 발생되도록 할 수 있습니다.
  • 특정 액션이 발생했을 때 특정 자바스크립트 함수를 실행시킬 수 있습니다.

 

4. 리듀서 (Reducer)

리듀서는 변화를 일으키는 함수입니다. 

현재의 상태(state)와 전달 받은 액션(action)을 참고하여 새로운 상태를 만들어 반환합니다. 

리덕스를 사용할 때 여러개의 리듀서를 만들고 이를 합쳐 루트 리듀서(Root Reducer)를 만들 수 있습니다. 

 

5. 스토어 (Store)

리덕스는 한 애플리케이션 당 하나의 스토어를 만들게 됩니다. 
스토어 안에는 현재의 앱 상태와 리듀서, 내장함수들이 포함되어 있습니다. 

 

6. 디스패치 (dispatch)

디스패치는 스토어의 내장함수 중 하나로, 액션을 발생시키는 역할을 합니다. 

dispatch 라는 함수에는 액션을 파라미터로 전달합니다. 

dispatch 를 호출하면, 스토어는 리듀서 함수를 실행시켜서 해당 액션을 처리하는 로직이 있다면 액션을 참고하여 새로운 상태를 만들어줍니다. 

 

7. 구독 (subscribe)
구독은 스토어의 내장함수 중 하나로, 함수 형태의 값을 파라미터로 받아옵니다. 

리액트에서 리덕스를 사용할 때 이 함수를 사용하는 경우는 별로 없고, react-redux 라는 라이브러리에서 제공하는 useSelector Hook 을 이용하여 리덕스 스토어의 상태를 구독하게 됩니다. 

 

 

 

Redux 기본 원칙

1. Single source of truth

  • 동일한 데이터는 항상 같은 곳에서 가지고 온다.
  • 즉, 스토어라는 하나뿐인 데이터 공간이 있다는 의미이다.

2. State is read-only

  • 리액트에서는 setState 메소드를 활용해야만 상태 변경이 가능하다.
  • 리덕스에서도 액션이라는 객체를 통해서만 상태를 변경할 수 있다.

3. Changes are made with pure functions

  • 변경은 순수함수로만 가능하다.
  • 리듀서와 연관되는 개념이다.
  • Store(스토어) – Action(액션) – Reducer(리듀서)

 

 

Redux 사용법 

 

예제

// 1. import
import { createAction, handleActions } from "redux-actions";
import { produce } from "immer";
import { api } from "../../shared/api";

// 2. actions(액션 타입)
const SET_USER = "SET_USER";
const LOG_OUT = "LOG_OUT";

// 3. action creators (액션 생성 함수)
const setUser = createAction(SET_USER, (user) => ({ user }));
const logOut = createAction(LOG_OUT, () => ({}));

// 4. initialState 초기값 설정
const initialState = {
  userId: null,
  userName: null,
  nickName: null,
  location: null,
  is_login: false,
};

const signUp = (id, nickName, location, pw, pwCheck) => {
  return function (dispatch, getState, { history }) {
    api
      .post("/user/signUp", {
        userName: id,
        nickName: nickName,
        location: location,
        passWord: pw,
        passWordCheck: pwCheck,
      })
      .then((res) => {
        window.alert("회원가입이 완료되었습니다. 로그인해주세요!");
        history.push("/");
      })
      .catch((err) => {
        window.alert(
          "회원가입에 실패했습니다. 비밀번호는 아이디를 포함할 수 없습니다."
        );
      });
  };
};

// 로그인
const logIn = (id, pw) => {
  return function (dispatch, getState, { history }) {
    console.log(id, pw);
    api
      .post("/user/logIn", {
        userName: id,
        passWord: pw,
      })
      .then((data) => {
        console.log(data);
        // 로컬스토리지에 token 저장
        localStorage.setItem("token", data.headers.authorization);

        dispatch(setUser({}));
        history.push("/main");
      })
      .catch((err) => {
        console.log(err);
      });
  };
};

// 4.3. isLogin
const isLogin = (Token) => {
  return function (dispatch, getState, { history }) {
    api
      .get("/user/isLogIn", {
        headers: {
          Authorization: ` ${Token}`,
        },
      })
      .then((res) => {
        const userId = res.data.userId;
        const userName = res.data.userName;
        const nickName = res.data.nickName;
        const location = res.data.location;
        dispatch(
          setUser({
            userId,
            userName,
            nickName,
            location,
          })
        );
      })
      .catch((err) => {
      });
  };
};

// 5. 로그아웃
const logout = () => {
  return function (dispatch, getState, { history }) {
    localStorage.removeItem("token");

    dispatch(logOut());
    history.replace("/");
  };
};

// 5. reducer
export default handleActions(
  {
    [SET_USER]: (state, action) =>
      produce(state, (draft) => {
        draft.userId = action.payload.user.userId;
        draft.userName = action.payload.user.userName;
        draft.nickName = action.payload.user.nickName;
        draft.location = action.payload.user.location;
        draft.is_login = true;
      }),

    [LOG_OUT]: (state, action) =>
      produce(state, (draft) => {
        draft.userName = null;
        draft.is_login = false;
      }),
  },
  initialState
);

// action creator export
const actionCreators = {
  signUp,
  logIn,
  isLogin,
  setUser,
  // mypostAPI,
  logout,
};

export { actionCreators };

 

 

Redux Toolkit 사용 (공식 추천)

공식문서는 Redux Toolkit 을 사용하여 Redux 로직을 작성하는 것을 추천합니다.

Redux Toolkit은 Redux 앱을 만들기에 필수적으로 여기는 패키지와 함수들을 포함합니다.  

 

 

 

 

참고

상태 관리 도구(State Management Tools)의 필요성

Redux(리덕스)란? (상태 관리 라이브러리)

[벨로퍼트] 6장. 리덕스, 7장 리덕스 미들웨어

[공식문서] Redux 시작하기

 

 

 

댓글