만족

[Nodejs] Redux의 기본 이해 본문

[Nodejs] Redux의 기본 이해

Nodejs Satisfaction 2021. 8. 27. 18:52

https://redux.js.org/tutorials/fundamentals/part-1-overview

 

Redux Fundamentals, Part 1: Redux Overview | Redux

The official Fundamentals tutorial for Redux: learn the fundamentals of using Redux

redux.js.org

위 내용을 참조하여 설명할 것이다.

 

Redux란 무엇인가?

Redux is a pattern and library for managing and updating application state, using events called "actions".
리덕스는 "액션"이라고 하는 이벤트를 사용하여 애플리케이션의 상태를 관리하고 업데이트하는 패턴이자 라이브러리입니다.

 

홈페이지에서는 다음과 같이 리덕스를 소개하고 있으며, 리덕스를 사용함으로써 전체 애플리케이션의 하나의 스토어를 통해 예측 가능한 형태로 상태를 변화시킬 수 있다고 한다.

 

즉 전역적으로 관리되어야 하는 상태를 리덕스를 통해 정해진 규칙 하에서 변경시킬 수 있다.

 

왜 Redux가 필요한가?

Redux helps you manage "global" state - state that is needed across many parts of your application.
The patterns and tools provided by Redux make it easier to understand when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur.

리덕스는 "전역"상태를 관리하는 데 도움을 줄 수 있다. 상태는 애플리케이션의 많은 부분에서 필요로 한다.

리덕스에서 제공하는 툴과 패턴들은 상태가 언제, 어떻게, 어디에서, 왜 변경되었는지와 상태가 변경되었을 때 애플리케이션이 어떻게 동작할 것인지를 이해하기 쉽게 해 준다.

 

Redux 없이도 전역 상태를 관리할 수 있다. 그러나 Redux를 쓰면 이러한 전역 상태를 정해진 규칙 하에서 관리할 수 있기 때문에 상태 변화에 따른 애플리케이션의 동작들을 더 쉽게 컨트롤할 수 있게 된다.

 

그렇다면 언제 Redux를 사용해야 하는가?

Redux is more useful when:
1. You have large amounts of application state that are needed in many places in the app
2. The app state is updated frequently over time
3. The logic to update that state may be complex
4.  The app has a medium or large-sized codebase, and might be worked on by many people

Not all apps need Redux. Take some time to think about the kind of app you're building, and decide what tools would be best to help solve the problems you're working on.

리덕스는 다음과 같은 상황에서 유용하다.
1. 앱에서 많은 곳에서 매우 많은 상태들이 있을 때
2. 앱의 상태가 시간이 지남에 따라 빈번히 변경되는 경우
3. 상태를 업데이트하는 로직이 복잡한 경우
4. 많은 사람들에 의해 관리되고 규모가 있는 앱인 경우

모든 앱이 리덕스가 필요하지는 않다. 당신이 어떤 앱을 빌드하는지와 해결하고자 하는 문제에 어떤 도구가 가장 적절한지를 생각해야만 한다.

 

Redux가 무엇인지, 왜 필요한지에 대해 설명했듯이

많은 사람들이 프로젝트를 관리하고 그 프로젝트 내에서 다양한 상태가 존재하며 그 상태에 얽힌 복잡한 로직이 존재할 경우

Redux라는 강력한 상태 관리 도구가 제공하는 상태 관리 규칙을 통해, 상태를 다루는 데 복잡성을 줄여준다.

 

A라는 사람과 B라는 사람이 상태를 다루는 방법이 각각 다를 수 있는데,

(또한 시간이 지남에 따라 A컴포넌트와 B컴포넌트의 상태 관리 방법이 달라질 수도 있다)

이를 Redux를 통해 강제로 동일한 방법으로 상태를 다루게 한다.

 

Redux를 이용해 상태 관리하기

리덕스에서 사용하는 요소들에 대해 먼저 알아보자.

 

리덕스에서는 상태를 저장하기 위한 store가 존재하며

상태는 action에 의해 변경되는데, dispatch를 통해 action을 발생시킬 수 있다.

또한 상태가 변경되었을 때 그 변경을 감지하기 위해 subscribe할 수 있다.

 

store: 상태를 보관하는 저장소

action: 상태를 어떻게 변경시킬지 정의한 데이터

dispatch: 스토어에 저장된 상태를 변경시키기 위해 action을 발생시키는 행위

subscribe: 스토어에 저장된 상태를 구독(변경될 경우 알림을 받을 수 있다)하는 행위

reducer: 현재 상태와 액션을 매개변수로 받아 새로운 상태를 리턴하는 함수(외부에서 액션 타입을 디스패치하면, 리듀서에서 이 액션 타입에 해당하는 액션에 따라 스토어에 저장된 상태를 변경시킨다)

 

<!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>
      // Define an initial state value for the app
      const initialState = {
        value: 0
      };

      // Create a "reducer" function that determines what the new state
      // should be when something happens in the app
      function counterReducer(state = initialState, action) {
        // Reducers usually look at the type of action that happened
        // to decide how to update the state
        switch (action.type) {
          case "counter/incremented":
            return { ...state, value: state.value + 1 };
          case "counter/decremented":
            return { ...state, value: state.value - 1 };
          default:
            // If the reducer doesn't care about this action type,
            // return the existing state unchanged
            return state;
        }
      }

      // Create a new Redux store with the `createStore` function,
      // and use the `counterReducer` for the update logic
      const store = Redux.createStore(counterReducer);

      // Our "user interface" is some text in a single HTML element
      const valueEl = document.getElementById("value");

      // Whenever the store state changes, update the UI by
      // reading the latest store state and showing new data
      function render() {
        const state = store.getState();
        valueEl.innerHTML = state.value.toString();
      }

      // Update the UI with the initial data
      render();
      // And subscribe to redraw whenever the data changes in the future
      store.subscribe(render);

      // Handle user inputs by "dispatching" action objects,
      // which should describe "what happened" in the app
      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>

reducer를 주목해 보자.

 

// Create a "reducer" function that determines what the new state
      // should be when something happens in the app
      function counterReducer(state = initialState, action) {
        // Reducers usually look at the type of action that happened
        // to decide how to update the state
        switch (action.type) {
          case "counter/incremented":
            return { ...state, value: state.value + 1 };
          case "counter/decremented":
            return { ...state, value: state.value - 1 };
          default:
            // If the reducer doesn't care about this action type,
            // return the existing state unchanged
            return state;
        }
      }

각 액션 타입별로 반환하는 상태를 보면, 

state.value= state.value+ 1처럼 원래 상태값을 변경시키는 대신

{...state, value: state.value+ 1}처럼 리턴한다.

(이전 상태는 건드리지 않고 변경을 원할 경우 새로운 객체를 생성하는 것을 immutable; 불변성을 가진다고 한다)

 

이렇게 되면 prevState와 nextState를 비교할 경우 참조 값이 다르게 되기 때문에,

외부에서 state가 변경되었는지 아닌지를 판단할 수 있게 된다.

 

문제점

Redux가 상태를 관리할 때 동일한 규칙을 제공한다는 것은 알겠다.

 

그러나 단순히 카운터값을 1 증가시키거나 감소시키는 기능을 구현하는 것임에도 불구하고,

매우 많은 양의 코드가 필요하다.

 

물론 이런 보일러플레이트를 줄여주는 라이브러리가 존재하긴 하지만,

어떤 행위를 편리하게 하기 위해 라이브러리를 사용하는데,

그 라이브러리를 또 편하게 하기 위해 다른 라이브러리를 사용하게 된다는 문제가 있다.

 

나 역시 이런 문제 때문에 요즘은 redux를 기피하는데,

최근 들어가보니 개발진들도 이런 문제를 해결해주기 위해 React의 경우 이런 번거로움을 줄여줄 수 있도록

reduxjs-toolkit이라는 라이브러리를 출시한 것을 확인할 수 있었다.

 

기회가 된다면, 다음에는 이 reduxjs-toolkit에 대해 포스트를 작성해 볼 예정이다.



Comments