카테고리 없음

[엘리스 12주차] Redux 튜토리얼

불곰자리 2023. 11. 30. 23:57

Redux 이론 강의에 이어 실습 강의에서도 Redux에 대해 배웠다.

Recoil에 대해서도 배웠지만 그건 나중에 적어보려고 한다.

 

오늘은 Redux 공식문서를 번역하면서 그에 대한 정보를 정리해보려고 한다. (Redux 기초 튜토리얼)

 

Redux란 무엇인가?

Redux는 애플리케이션의 상태를, action이라는 이벤트를 사용해 업데이트하고 관리하는 라이브러리이며 동시에 패턴이다. 이 action은  전체 애플리케이션에서 사용해야 하는 상태에 대한 중앙 저장소 역할을 하며, 상태를 예측 가능한 방식으로만 업데이트할 수 있도록 규칙을 제공한다. 

 

Redux를 사용해야 하는 이유

애플리케이션 내 여러 곳에서 사용되어야 하는 "전역" 변수를 관리하는 것을 도와준다. 

Redux에 의해 제공되는 패턴과 도구들은, 애플리케이션 내 상태가 언제, 어디서, 왜, 그리고 어떻게 업데이트될 지, 변화가 일어날 때 어떤 로직이 수행될 지 이해하기 쉽게 해준다.

 

Redux를 사용해야 하는 때

Redux는 공유가 필요한 상태들을 관리하는 것을 도와주지만, 다른 도구(라이브러리)와 같이, 그에 따른 단점도 존재한다. 배워야하는 개념이나, 전역 상태 관리를 위해 추가적으로 작성해야 하는 코드들이 존재하기 때문이다. 코드에 방향성이나 제한 사항 또한 존재한다. 

 

Redux는 이럴 때 유용하다

  • 애플리케이션 내에 많은 곳에서 필요한 상태가 많을 때
  • 애플리케이션의 상태자주 업데이트 되어야 할 때
  • 상태를 업데이트 하기 위한 로직이 복잡할 때
  • 꽤 규모가 있는 길이의 코드 모음이거나, 많은 사람들이 해당 코드에 대해 작업해야할 때

Redux 라이브러리와 도구들

Redux 자체는 작고 독립적인 자바스크립트 라이브러리이다. 그러나, 여러 패키지들과 함께 사용되는 것이 흔하다.

React-Redux

Redux는 어떤 UI 프레임워크와도 확장이 가능하지만, 주로 React와 함께 쓰인다. React-Redux는 몇몇 상태를 읽고 store를 업데이트하기 위해 actiondispatch 함으로써 React 컴포넌트와 상호작용할 수 있도록 한다.

 

Redux Toolkit

Redux ToolkitRedux 로직 작성을 위해 추천되는 접근 방식이다. (실제로 Redux Toolkit을 쓸 것을 권장하고 있으며, 왜 권장하는지에 대한 이유가 꽤 길게 작성되어있다.) Redux 애플리케이션을 빌드하는 데에 필요하다고 생각되는 패키지나 함수를 포함하고 있다. Redux Toolkit은 모범 사례에 기반한 빌드를 수행하며, Redux를 사용하는 데 필요한 코드의 작성을 단순화, 실수를 방지할 수 있으며 Redux 애플리케이션을 쉽게 사용할 수 있도록 한다.

 

Redux 기초

기초에 대한 정보는 Redux의 코어 라이브러리에 대해서만 집중적으로 설명한다.

Redux Store

모든 Redux 애플리케이션의 중심은 store이다. store는 당신의 애플리케이션 전역 상태를 가지고있는 컨테이너다.

또한 store는 일반 전역 객체와는 다른 몇 개의 함수와 이점을 지닌 자바스크립트 객체이다. 

  • Redux store 내에 있는 상태를 직접적으로 수정하거나 변화시켜서는 안된다.
  • 대신, 상태에 대한 유일한 업데이트 방법으로 "애플리케이션 내에 어떤 이벤트가 발생했다"는 설명과 함께 action 객체를 생성하여 store에 해당 actiondispatch하여 어떤 이벤트가 발생했는지 알리는 방법이 있다.
  • actiondispatch되면, store는 가장 위의 reducer 함수를 실행하고, 이전 상태와 전달된 action을 기반으로 새 상태를 계산한다. 
  • 마침내, storesubscribers(해당 상태를 사용하는 컴포넌트)에 업데이트 정보를 알리고, UI는 새 데이터와 함께 업데이트 된다.
<!DOCTYPE html>
<html>
  <head>
    <title>Redux basic example</title>
    <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
  </head>
  <body>
    <div>
      <p>
        Clicked: <span id="value">0</span> times
        <button id="increment">+</button>
        <button id="decrement">-</button>
        <button id="incrementIfOdd">Increment if odd</button>
        <button id="incrementAsync">Increment async</button>
      </p>
    </div>
    <script>
      // 초기 값을 정의한다.
      const initialState = {
        value: 0
      };

      // 어떤 이벤트가 발생했을 때, 어떤 변화가 발생할 지 결정하는
      // reducer 함수를 생성한다.
      function counterReducer(state = initialState, action) {
        // Reducer는 발생한 이벤트의 타입과 어떤 식으로 데이터가 업데이트될 지 주로 본다.
        switch (action.type) {
          case "counter/incremented":
            return { ...state, value: state.value + 1 };
          case "counter/decremented":
            return { ...state, value: state.value - 1 };
          default:
            // action의 타입에 신경쓰지 않는다면
            // 현재 상태 그대로를 반환한다.
            return state;
        }
      }

      // createStore 함수로 새로운 Redux store를 생성하고,
      // 로직 업데이트를 위한 counterReducer를 사용한다.
      const store = Redux.createStore(counterReducer);

      // UI는 단일 HTML 요소 내의 텍스트이다.
      const valueEl = document.getElementById("value");

      // store 상태가 변하면, 가장 최근 store 상태를 읽고
      // 새로 만든 데이터를 UI에 업데이트한다.
      function render() {
        const state = store.getState();
        valueEl.innerHTML = state.value.toString();
      }

      // 초깃값 데이터로 UI를 업데이트
      render();
      // 데이터의 변화가 있을 때 UI를 다시 그리기 위해 subscribe를 한다.
      store.subscribe(render);

      // 애플리케이션 내에서 어떤 이벤트가 발생하는지 설명하고
      // action 객체를 dispatch 함으로써 유저 입력을 처리한다.
      document
        .getElementById("increment")
        .addEventListener("click", function () {
          store.dispatch({ type: "counter/incremented" });
        });

      document
        .getElementById("decrement")
        .addEventListener("click", function () {
          store.dispatch({ type: "counter/decremented" });
        });

      document
        .getElementById("incrementIfOdd")
        .addEventListener("click", function () {
          // We can write logic to decide what to do based on the state
          if (store.getState().value % 2 !== 0) {
            store.dispatch({ type: "counter/incremented" });
          }
        });

      document
        .getElementById("incrementAsync")
        .addEventListener("click", function () {
          // We can also write async logic that interacts with the store
          setTimeout(function () {
            store.dispatch({ type: "counter/incremented" });
          }, 1000);
        });
    </script>
  </body>
</html>

 

Redux는 의존성이 없는 독립적인 자바스크립트 라이브러리이기 때문에, Redux 라이브러리를 위한 단일 스크립트 태그만으로 예제와 같은, 자바스크립트와 HTML만을 사용한 코드 작성이 가능하다. 실제로, Redux는 npm으로 패키지를 설치함으로써 사용이 가능하고, UI는 React 라이브러리를 사용하는 것처럼 생성이 가능하다.

 

State, Actions, 그리고 Reducers

Redux는, 애플리케이션 내에서 초기 상태 값을 정의함으로써 시작할 수 있다.

const initialState = {
  value: 0
}

 

 

Redux 애플리케이션은 일반적으로 자바스크립츠 객체를 루트 내의 상태 조각으로 가지고 있으며, 다른 (정의한) 값들도 해당 객체 안에 존재한다.

 

그 다음으론, reducer 함수를 정의한다. reducer는 두 개의 인자를 받는데, 현재의 상태와 어떤 이벤트가 발생했는지 서술하는 action 객체이다.Redux 애플리케이션이 실행되면, 아직 아무 상태도 가지고 있지 않다가, initialState(초기값)을 설정함으로써 reducer에 값을 제공한다.

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { ...state, value: state.value + 1 }
    case 'counter/decremented':
      return { ...state, value: state.value - 1 }
    default:
      return state
  }
}

 

action 객체는, 해당 action에 대해 유일한 이름을 가지는 문자열 값인 type 필드를 항상 가지고 있다. type은 누가 봐도 이해할 수 있을법한 이름이어야 한다. 위 예시의 경우, 앞에 'counter'라는 단어를 사용하였고, 뒤에 어떤 "이벤트"가 발생했는지 서술하고 있다. 이 경우, 'counter'가 'incremented'(증가)되었을 때를 뜻하기 때문에, 'counter/incremented'라고 type을 작성한다.

 

action의 type에 기반하여, 새 상태 값이 될 객체를 반환하거나 필요가 없다면 변화하지 않은 상태 그대로를 반환한다. 상태 업데이트 시, 존재하는 상태 값을 복사하고, 기존 값을 직접적으로 수정하지 않고 복사된 값을 업데이트함으로써 상태값을 immutable(불변)하도록 한다. 

 

Store

reducer 함수가 존재하기 때문에, createStore API를 호출해 store 인스턴스를 생성할 수 있다.

const store = Redux.createStore(counterReducer)

 

 

reducer 함수를 createStore에 넘겨주고, reducer 함수가 초기 상태 값을 생성하도록하고, 추후에 업데이트 될 값을 계산하도록 한다.

 

UI 

어느 애플리케이션에서도, UI는 화면에 현재 상태 값을 보여준다. 그리고 유저가 어떤 행동을 하면, 애플리케이션은 데이터를 업데이트하고 업데이트 한 값을 통해 UI를 다시 그린다.

const valueEl = document.getElementById('value')

function render() {
  const state = store.getState()
  valueEl.innerHTML = state.value.toString()
}

render()
store.subscribe(render)

 

이 예제에선, 현재 값을 보여주는 <div> 태그, 기초적인 HTML 요소를 UI에 사용하고 있다. 

다음으로, store.getState() 메소드를 사용해 Redux store에서 최신 상태 값을 알 수 있도록 하고, 해당 값을 통해 UI를 업데이트하여 사용자에게 보여준다.  Redux storestore.subscribe()를 호출하여 store가 업데이트 되었을 때마다 호출되는 subscriber 콜백 함수를 전달한다. 그 다음, render 함수를 subscriber로써 전송할 수 있고, store가 업데이트 될 때마다, 가장 최신 값으로 UI을 업데이트 할 수 있다. Redux 그 자체는 어디에서나 쓰일 수 있는 독립적인 라이브러리이다. 이는 어떤 UI 레이어에서도 쓰일 수 있다는 것을 뜻한다.

 

Dispatching Actions

마침내, 어떤 이벤트가 발생했는지 서술하는 action 객체를 생성, store에 그것들을 dispatch하여 UI에 응답을 전달할 수 있게 되었다. store.dispatch(action) 메서드를 호출하면, storereducer를 실행하며, 업데이트가 필요한 상태 값을 계산하고, subscriber에게 UI를 업데이트하도록 한다.

document.getElementById('increment').addEventListener('click', function () {
  store.dispatch({ type: 'counter/incremented' })
})

document.getElementById('decrement').addEventListener('click', function () {
  store.dispatch({ type: 'counter/decremented' })
})

document
  .getElementById('incrementIfOdd')
  .addEventListener('click', function () {
    // We can write logic to decide what to do based on the state
    if (store.getState().value % 2 !== 0) {
      store.dispatch({ type: 'counter/incremented' })
    }
  })

document
  .getElementById('incrementAsync')
  .addEventListener('click', function () {
    // We can also write async logic that interacts with the store
    setTimeout(function () {
      store.dispatch({ type: 'counter/incremented' })
    }, 1000)
  })

 

이 외에도 어떤 상태의 참/거짓 여부 계산이나, 조금의 여유를 두고 dispatch를 실행하는 비동기 코드 또한 작성할 수 있다.

 

데이터 흐름

Redux 애플리케이션를 통한 데이터의 흐름은 아래의 다이어그램으로 요약할 수 있다.

아래 다이어그램은 

  • 클릭과 같은 유저와의 상호작용에 어떻게 actiondispatch가 일어나는지
  • store가 새로운 상태 값을 계산하는 reducer함수를 어떻게 작동시키는지
  • UI가 새로운 상태 값을 어떻게 읽어 화면에 반영하는지

알려준다.

Redux 애플리케이션 내에서의 데이터 흐름

 

이때까지의 작성 글을 요약하면 다음과 같다.

  •  Redux는 전역 애플리케이션 상태 관리를 위한 라이브러리다.
    • ReduxReact를 함께 사용하기 위해 React-Redux 라이브러리를 일반적으로 사용한다.
    • Redux ToolkitRedux 로직 작성을 위해 추천되는 방법이다.
  • Redux는 여러 코드의 타입을 사용한다.
    • Action은 type 필드를 가지는 간단한 객체이며, 애플리케이션 내에서 "무슨 이벤트가 발생했는지" 서술한다.
    • Reducer는 이전 상태 값과 action을 기반으로 새 상태 값을 계산하는 함수다.
    • Redux storeactiondispatch될 때마다 가장 위에 존재하는 reducer를 실행한다.

오늘 적은 내용과 별개로, 공식 문서를 참고해서 내용을 정리하는 기회가 있는 건 좋은 것 같다.

 

#엘리스트랙 #엘리스트랙후기 #리액트네이티브강좌 #온라인코딩부트캠프 #온라인코딩학원 #프론트엔드학원 #개발자국비지원 #개발자부트캠프 #국비지원부트캠프 #프론트엔드국비지원 #React #Styledcomponent #React Router Dom #Redux #Typescript #Javascript