Redux 코드 및 원리이해

jiseong·2022년 6월 8일
0

T I Learned

목록 보기
264/291
post-custom-banner

최근에 옵저버패턴을 활용하여 중앙저장소 개념으로 상태를 관리해본적이 있었다. 어플리케이션이 비대해질수록 어떤 상태를 공유하기 위해서 depth가 깊은 하위 컴포넌트로 상태를 넘겨주면서 서로간의 의존성이 높아지는 현상이 잦게되는데 의존성이 높은 것은 좋은 현상이 아니다. 그래서 옵저버패턴을 활용하여 컴포넌트에 종속된 상태관리를 분리하여 외부에서 관리하도록 했다.

상태관리 라이브러리로 자주 사용되는 리덕스 또한 옵저버 패턴을 기반으로 작성되었는데 한동안 사용을 안하다보니 기억이 가물가물해서 이번 기회에 다시 사용해보면서 원리도 되짚어보려고 한다.

리덕스는 크게 Action, Dispatch, Reducer, Store으로 나눌 수 있다.

Action

Action(액션)은 데이터의 상태를 어떻게 바꿀지에 대한 명령으로 볼 수 있으며
Action은 새로 발생한 액션의 타입과 데이터 페이로드를 액션 메시지로 이루어진 객체 형식이다.

type: 필수
payload: 교체할 상태 값

<!DOCTYPE html>
<html lang="ko">
<head>생략...</head>
<body>
  <h1></h1>
  <div></div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/5.0.0-alpha.0/redux.min.js"></script>
  <script>
    // 생략...
    
    // 액션 정의
    const CHANGE_NAME = 'CHANGE_NAME';
    const actionChangeName = (newName) => {
      return {
        type: CHANGE_NAME,
        payload: newName
      }
    }
  </script>
</body>
</html>

Dispatch(action)

Dispatch(디스패츠)는 상태변경을 트리거하는 유일한 방법으로 Action객체를 Reducer에 전달하기 위해서는 해당 메소드를 사용해야한다.

store.dispatch(actionChangeName(name)); // 액션객체를 dispatch()메서드의 인자로 넘긴다.

아래의 예시는 입력창에 입력된 이름을 Dispatch()메서드를 사용해서 상태변경을 트리거하려는 예시이다.

<!DOCTYPE html>
<html lang="ko">
<head>생략...</head>
<body>
  <h1></h1>
  <div></div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/5.0.0-alpha.0/redux.min.js"></script>
  <script>
    // 생략...
    Form();
    function Form() {
      document.querySelector('div').innerHTML = `
        <form>
          <p><input type="text" name="name" placeholder="바꿀 이름을 입력해주세요."></p>
          <p><input type="submit" value="수정"></p>
        </form>
      `;
    }
  </script>
</body>
</html>

Reducer

액션을 스토어에 바로 전달하여 상태를 업데이트 할 수도 있지만 리덕스에서는 리듀서에 전달하여 리듀서가 스토어의 상태를 업데이트 하는 방식이다.

Reducer(리듀서)는 이전 상태값과 액션 객체를 인자로 받아서 새로운 상태값을 만드는 순수함수이다.

상태는 읽기전용이기 때문에 기존의 상태를 건드리지 않고 Object.assign 을 사용하거나 spread 연산자 (...)를 사용하여 새로운 상태값으로 만들어야 한다.

<!DOCTYPE html>
<html lang="ko">
<head>생략...</head>
<body>
  <h1></h1>
  <div></div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/5.0.0-alpha.0/redux.min.js"></script>
  <script>
    // 생략...
    
    // reducer 함수
    const userReducer = (state = initState, action) => {
      switch(action.type) {
        case CHANGE_NAME: {
          return {
            ...state,
            name: action.payload
          }
        }
        default:
          return state;
      }
    }

  </script>
</body>
</html>

새로운 상태로 교체하는 것이 중요!!

Store

Store(스토어)는 상태가 관리되는 오직 하나의 공간이다.
별개의 스토어라는 공간이 있어서 그 스토어 안에 앱에서 필요한 상태를 담는다.

<!DOCTYPE html>
<html lang="ko">
<head>생략...</head>
<body>
  <h1></h1>
  <div></div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/5.0.0-alpha.0/redux.min.js"></script>
  <script>
    // store 생성 및 리듀서 등록
    const store = Redux.createStore(userReducer);
  </script>
</body>
</html>

지금까지의 흐름을 그림으로 나타내면 다음의 그림이 정말 잘 나타낸다고 생각한다. (미들웨어는 비동기로직을 처리하기 위해 나온 개념으로 여기서는 무시해도 괜찮다.)

다만, 그림을 보면서 왜 이런 흐름으로 작성을 해야하는지 궁금했었다.

이런 공식을 왜 따를까?

그림을 계속쳐다보고 있으면 데이터가 한 방향으로만 흘러가는 것을 볼 수 있다. 이는 Flux 패턴과 관련된 것인데 Flux가 출현하게 된 배경을 생각해보면 이해가 된다.

추천하는 게시글!: https://www.huskyhoochu.com/flux-architecture/

간단히 설명하자면 기존의 어플리케이션에서 보편적으로 사용되는 MVC패턴에서는 Model이 View를 반영하고, View가 Model을 변경하는 양방향 데이터 흐름으로 인해 각 모델에서 발생한 이벤트가 어떤 변화를 일으킬지에 대한 예측하기가 어렵다는 문제가 있었다. 그래서 facebook에서는 이러한 문제를 해결하기 위해 Flux패턴을 만든 것이고 이로인해 대규모 어플리케이션에서 보다 일관된 데이터 관리를 할 수 있게 된 것이다.

마지막

내가 테스트한 코드이다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux</title>
</head>
<body>
  <h1></h1>
  <div></div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/5.0.0-alpha.0/redux.min.js"></script>
  <script>
    // data 기본
    const initState = { name: 'who are you' }

    // action 객체
    const CHANGE_NAME = 'CHANGE_NAME';
    const actionChangeName = (newName) => {
      return {
        type: CHANGE_NAME,
        payload: newName
      }
    }
    
    // reducer 함수
    const userReducer = (state = initState, action) => {
      switch(action.type) {
        case CHANGE_NAME: {
          return {
            ...state,
            name: action.payload
          }
        }
        default:
          return state;
      }
    }
    // store 생성 및 리듀서 등록
    const store = Redux.createStore(userReducer);

    Form();
    Title();
    
    // 데이터 변화가 일어나면 Title함수를 다시 호출하겠다는 의미의 구독
    store.subscribe(Title);

    function Title() {
      const { name } = store.getState();

      document.querySelector('h1').innerHTML = `
        <span>${name}</span>
      `;
    }

    function Form() {
      document.querySelector('div').innerHTML = `
        <form>
          <p><input type="text" name="name" placeholder="바꿀 이름을 입력해주세요."></p>
          <p><input type="submit" value="수정"></p>
        </form>
      `;
    }
  </script>
</body>
</html>

Reference

post-custom-banner

0개의 댓글