[Redux] Reudx Overview and Concepts (번역, 정리)

chaevivi·2023년 10월 20일
0
post-thumbnail

리덕스 개요와 개념


💡 이 챕터에서 배우는 내용

  • Redux가 무엇이고 사용하는 이유
  • 중요 Redux 용어와 개념들
  • Redux 앱에서 data가 흐르는 방식


1. Redux란?

Redux는 "actions"라고 불리는 이벤트를 사용하여 애플리케이션 상태를 관리하고 업데이트하기 위한 패턴 및 라이브러리입니다.

  • 리덕스는 상태가 예측 가능한 방식으로만 업데이트될 수 있도록 보장하는 규칙을 가지고 있습니다.
  • 해당 규칙을 이용하여 전체 애플리케이션에서 사용되는 상태에 대해 중앙 집중식 저장소 역할을 합니다.

1.1. Redux를 사용해야 하는 이유

  • 리덕스는 애플리케이션의 많은 부분에서 필요한 "전역" 상태를 관리할 수 있도록 도와줍니다.
  • 리덕스에 의해 제공되는 패턴과 도구들은 상태가 애플리케이션 상에서 언제, 어디서, 왜, 어떻게 업데이트되는지와 상태가 업데이트될 때 애플리케이션 로직에서 어떤 일이 일어나는 지 이해하기 쉽도록 도와줍니다.
  • 리덕스는 예측 가능하고 테스트 가능한 코드를 작성할 수 있도록 도와줍니다.

1.2. Redux를 언제 사용해야 할까?

  • 리덕스가 유용한 때
    • 앱의 상태가 자주 업데이트될 때
    • 앱의 많은 곳에서 사용되는 많은 양의 상태가 있을 때
    • 상태를 업데이트하는 로직이 복잡할 때
    • 앱에 중간 또는 대규모의 코드베이스가 있고, 많은 사람과 일할 때
  • 단점
    • 더 많은 개념들을 배워야 하고, 더 많은 코드를 작성해야 합니다.
    • 코드에 간접적인 내용을 추가하고 특정 제한 사항을 따라야 합니다.
  • 모든 앱에서 리덕스가 필요하지는 않습니다. 어떤 앱을 만드는지 생각해보고 어떤 도구를 가지고 문제를 해결할 것인지 결정해야 합니다.

1.3. Redux 라이브러리와 도구

  • 리덕스는 작은 독립형 자바스크립트 라이브러리입니다. 하지만 일반적으로 여러 패키지들과 함께 사용됩니다.

(1) React-Redux

  • 리덕스는 어떤 UI 프레임워크든지 통합 가능하고, 대부분 React와 자주 사용됩니다.
  • React-Redux는 공식적인 패키지로, 리액트 컴포넌트가 상태의 부분을 읽고, 스토어를 업데이트하기 위해 액션을 파견함으로써 리액트 컴포넌트가 리덕스 스토어와 상호작용할 수 있도록 도와줍니다.

(2) Redux-Toolkit

  • 리덕스 툴킷은 리덕스 로직 작성에 권장되는 접근법입니다.
  • 리덕스 툴킷은 리덕스 앱의 필수 패키지와 함수를 포함하고 있습니다.
  • 리덕스가 하는 작업들을 간소화하고, 일반적인 실수를 예방하고, 리덕스 애플리케이션 작성을 더 쉽게 만들어 줍니다.

(3) Redux DevTools Extension

  • 리덕스 데브툴스 익스텐션은 리덕스 스토어의 모든 상태 변화 히스토리를 보여줍니다.
  • "시간 여행 디버깅" 같은 강력한 기술을 포함하고 있기 때문에 효과적으로 애플리케이션을 디버깅할 수 있습니다.


2. Redux 용어와 개념


2.1. 상태 관리 (State Management)

  • React Counter 컴포넌트

    • 컴포넌트 상태의 숫자를 추적하고, 버튼이 클릭되면 숫자가 증가합니다.
    function Counter() {
      // State: 카운터 값
      const [counter, setCounter] = useState(0);
    
      // Action: 어떤 일이 일어나면 상태를 업데이트하는 코드
      const increment = () => {
        setCounter(prevCounter => prevCounter + 1);
      };
    
      // View: UI 정의
      return (
        <div>
            Value: {counter} <button onClick={icrement}>Increment</button>
        </div>
      )
    }
    • 이 컴포넌트는 아래와 같은 부분들로 구성된 독립형 앱입니다.
      • state(상태), 앱을 구동하는 진실의 원천
      • view(뷰), 현재 상태를 기반으로 한 UI
      • actions(액션), 사용자 입력을 기반으로 앱에서 발생하고, 상태를 업데이트하는 이벤트
    • 위의 코드는 "단방향 데이터 플로우(one-way data flow)" 의 예시입니다.
      • 상태는 특정한 시점의 앱 상태를 설명합니다.
      • UI는 상태를 기반으로 렌더링됩니다.
      • 어떤 일이 일어나면(버튼 클릭), 상태는 어떤 일이 발생했는 지를 기반으로 업데이트됩니다.
      • UI는 새로운 상태를 기반으로 리렌더링됩니다.
    • 이 단방향 데이터 플로우는 똑같은 상태를 여러개의 컴포넌트에서 공유하고 사용할 때 무너질 수 있습니다.
      • 해결 방법은 컴포넌트로부터 상태를 추출하여 컴포넌트 트리 바깥의 중앙에 위치한 곳에 놓는 것입니다.
      • 이렇게 하면 컴포넌트 트리는 하나의 큰 "view"가 되고, 컴포넌트 트리의 어떤 컴포넌트라도 상태에 접근하거나 액션을 발생시킬 수 있습니다.
      • 상태 관리와 관련된 개념을 정의 및 분리하고 뷰와 상태 사이의 독립성을 유지함으로써, 코드를 더 구조적이고 유지 보수성이 좋도록할 수 있습니다.
      • 이는 리덕스의 기본 아이디어입니다.

2.2. 불변성 (Immutability)

  • 불변성은 절대 변할 수 없다는 것을 의미합니다.

  • 자바스크립트 객체와 배열은 기본적으로 모두 변할 수 있습니다.

    • 만약 객체나 배열을 생성하다면, 해당 필드의 내용을 바꿀 수 있습니다.

      const obj = { a: 1, b: 2 };
      // 바깥에서는 여전히 똑같은 객체이지만, 내용은 변경됨
      obj.b = 3;
      
      const arr = ['a', 'b'];
      // 똑같은 방법으로, 배열의 내용 변경 가능 
      arr.push('c');
      arr[1] = 'd';
    • 이를 객체 또는 배열 변경이라고 합니다.


  • 값을 불변하게 업데이트하기 위해서는 객체와 배열의 복사본을 만들고 복사본을 수정해야 합니다.

    • 자바스크립트의 배열/객체 스프레드 연산자를 사용하면 불변하게 값을 업데이트할 수 있습니다. 또한, 원본 배열을 변경하는 대신에 새로운 배열의 복사본을 반환하는 배열 메서드도 있습니다.
    const obj = {
      a: {
        // obj.a.c를 안전하게 업데이트하기 위해 각각을 복사해야 함
        c: 3
      },
      b: 2
    };
    
    const obj = {
      // obj 복사
      ...obj,
      // a 오버라이딩
      a: {
        // obj.a 복사
        ...obj.a,
        // c 오버라이딩
        c: 42
      }
    }
    
    const arr = ['a', 'b'];
    // arr의 새로운 복사본을 만들고 끝에 "c" 추가
    const arr2 = arr.concat('c');
    
    // 또는 원본 배열의 복사본 생성
    const arr3 = arr.slice();
    // 그리고 복사본 변경
    arr3.push('3');
  • 리덕스는 모든 상태가 불변적으로 업데이트될 것이라고 예상합니다.


2.3. 용어 (Terminology)

  • 액션 (Actions)
    • 액션은 애플리케이션에서 발생하는 무언가를 설명하는 이벤트입니다.
    • 액션은 type 필드를 가진 순수 자바스크립트 객체입니다.
      • type 필드는 문자열이어야 합니다. 보통 "domain/eventName" 처럼 작성합니다.
        • 첫 번째 부분(domain)은 액션이 속해 있는 특징이나 카테고리
        • 두 번째 부분(eventName)은 일어난 구체적인 일
      • 액션 객체는 무슨 일이 일어났는지에 대한 추가적인 정보를 포함하는 다른 필드를 가질 수 있습니다. 관례적으로, 이 정보는 payload라고 불리는 필드에 넣습니다.
      • 전형적인 액션 객체
        const addTodoAction = {
          type: 'todos/todoAdded',
          payload: 'Buy milk'
        };

  • 액션 생성자 (Action Creators)
    • 액션 생성자는 액션 객체를 생성하고 반환하는 함수입니다.
    • 액션 생성자를 사용하면 매번 직접 액션 객체를 쓰지 않아도 됩니다.
    const addTodo = text => {
      return {
        type: 'todos/todoAdded',
        payload: text
      };
    };

  • 리듀서 (Reducers)

    • 리듀서는 현재 상태와 액션 객체를 받고, 필요에 따라 어떻게 상태를 업데이트할 것인지 결정하고, 새로운 상태를 반환하는 함수입니다.

      • (state, action) => newState
    • 리듀서는 받아온 액션(이벤트) 타입을 기반으로 이벤트 처리하는 이벤트 리스너로 생각할 수 있습니다.

    • 리듀서는 특정한 규칙들을 따라야 합니다.

      • 리듀서는 stateaction 인자를 기반으로 한 새로운 상태 값만 계산합니다.
        • 리듀서는 기존의 상태를 수정할 수 없습니다. 대신에, 그들은 기존의 상태를 복사하고 복사된 값을 변경하는 불변한 업데이트를 만들어야 합니다.
        • 리듀서는 동기적인 로직을 실행하거나, 무작위 값을 계산하거나, 다른 부작용을 절대 유발하지 않습니다.
    • 리듀서 함수의 로직 안에서는 일반적으로 다음의 단계를 따라갑니다.

      • 리듀서가 이 액션과 관련있는지 확인합니다.
        • 관련있다면, 상태의 복사본을 만들고, 새로운 값의 복사본을 업데이트하고 반환합니다.
        • 그렇지 않으면, 변경되지 않은 기존의 상태를 반환합니다.
      const initialState = { value: 0 };
      
      function cunterReducer(state = initialState, action) {
        // 리듀서가 이 액션과 관련있는지 확인
        if (action.type === 'counter/increment') {
          // 관련있다면, 상태의 복사본을 만듦
          return {
            ...state,
            // 새로운 값의 복사본 업데이트
            value: state.value + 1
          };
        }
        // 그렇지 않으면, 변경되지 않은 기존의 상태를 반환
        return state;
      }
    • 리듀서는 새로운 상태 (if/else, switch문, 반복문 등)를 결정하기 위해 내부의 모든 로직을 사용할 수 있습니다.


  • 스토어 (Store)

    • 현재 Redux 애플리케이션 상태는 스토어라고 불리는 객체 안에 있습니다.

    • 스토어는 리듀서를 전달하여 생성되고, 현재 상태 값을 반환하는 getState 메서드를 가지고 있습니다.

      import { configureStore } from '@reduxjs/toolkit';
      
      const store = configureStore({ reducer: counterReducer });
      
      console.log(store.getState());    // {value: 0}

  • 디스패치 (Dispatch)

    • 리덕스 스토어는 dispatch라고 불리는 메서드를 가지고 있습니다.

    • 상태를 업데이트할 수 있는 유일한 방법은 store.dispatch()를 호출하고 액션 객체를 전달하는 것입니다.

    • 스토어는 리듀서 함수를 실행하고 내부에 새로운 상태 값을 저장하고, getState()를 호출하여 업데이트된 값을 검색할 수 있습니다.

      store.dispatch({ type: 'counter/increment' });
      
      console.log(store.getState());    // {value: 1}
    • 액션을 디스패치하는 것을 애플리케이션 상에서 "이벤트가 발생"하는 것처럼 생각할 수 있습니다.

      • 리듀서는 이벤트 리스너처럼 동작하고, 관련있는 액션이 있으면 응답으로 상태를 업데이트합니다.
    • 올바른 액션을 디스패치 하기 위해 일반적으로 액션 생성자를 호출합니다.

      const increment = () => {
        return {
          type: 'counter/increment'
        };
      };
      
      store.dispatch(increment());
      
      console.log(store.getState());    // {value: 2}

  • 선택자 (Selectors)

    • 선택자는 스토어 상태 값의 특정한 정보의 부분들을 추출하는 함수입니다.
    • 애플리케이션의 규모가 커질수록 똑같은 데이터를 읽어야 하는 반복되는 로직을 피하는데 도움을 줍니다.
    const selectCounterValue = state => state.value;
    
    const currentValue = selectCounterValue(store.getState());
    console.log(currentValue);     // 2

2.4. Redux 애플리케이션 데이터 흐름

  • "단방향 데이터 흐름"은 앱을 업데이트하는 일련의 과정을 설명합니다.
    • 상태는 특정한 시점의 앱 상태를 설명합니다.
    • UI는 상태를 기반으로 렌더링됩니다.
    • 어떤 일이 일어나면, 상태는 어떤 일이 발생했는 지를 기반으로 업데이트됩니다.
    • UI는 새로운 상태를 기반으로 리렌더링됩니다.
  • 리덕스에서는 일련의 과정들을 더 자세히 분석할 수 있습니다.
    • 초기 설정
      • 리덕스 스토어는 루트 리듀서를 한 번 호출하고, 반환값을 초기 상태로 저장합니다.
      • UI가 처음 렌더링 됐을 때, UI 컴포넌트는 리덕스 스토어의 현재 상태에 접근하고 어떤 것을 렌더링할 것인지 정하기 위해 데이터를 사용합니다. 또한, UI 컴포넌트는 미래의 스토어 업데이트를 구독하여 상태가 변화했는지 알 수 있습니다.
    • 업데이트
      • 사용자가 버튼을 클릭한 것처럼 앱에서 어떤 일이 일어납니다.
        • 앱의 코드는 dispatch({ type: 'counter/increment' }) 처럼 리덕스 스토어에 액션을 디스패치합니다.
        • 스토어는 이전의 상태와 현재의 상태를 가지고 다시 리듀서 함수를 실행하고, 반환값을 새로운 상태로 저장합니다.
        • 스토어는 구독했던 UI의 모든 부분들에게 스토어가 업데이트 되었다고 알립니다.
        • 스토어의 데이터가 필요한 각각의 UI 컴포넌트는 필요한 상태의 부분이 변경되었는지 확인합니다.
        • 데이터가 변경된 각각의 컴포넌트는 새로운 데이터로 강제 리렌더링됩니다.
    • 데이터 흐름 그림


3. 정리

  • 리덕스는 전역 애플리케이션 상태를 관리하기 위한 라이브러리입니다.
    • 리덕스는 일반적으로 리덕스와 리액트를 통합하기 위햔 React-Redux 라이브러리로 사용됩니다.
    • 리덕스 툴킷은 리덕스 로직을 작성하는 데 권장하는 방식입니다.
  • 리덕스는 "단방향 데이터 흐름" 앱 구조에서 사용합니다.
    • 상태는 특정한 시점의 앱 상태를 설명하고, UI는 해당 상태에 기반해 렌더링됩니다.
    • 앱에서 어떤 일이 일어나면,
      • UI는 액션을 디스패치합니다.
      • 스토어는 리듀서를 실행하고, 상태는 어떤 일이 일어났는지에 기반해 업데이트됩니다.
      • 스토어는 상태가 변경됐다고 UI에 알립니다.
    • UI는 새로운 상태에 기반해 리렌더링됩니다.
  • 리덕스는 여러 타입의 코드를 사용합니다.
    • 액션은 type 필드의 순수 객체이고, 앱에서 "어떤 일이 일어났는지"를 설명합니다.
    • 리듀서는 이전 상태 + 액션에 기반한 새로운 상태 값을 계산하는 함수입니다.
    • 리덕스 스토어는 액션이 디스패치될 때마다 루트 리듀서를 실행합니다.



출처
🔗 공식 문서: https://ko.redux.js.org/tutorials/essentials/part-1-overview-concepts
🔗 Github: https://github.com/chaevivin/Front-end_study/blob/main/Redux/Redux_Overview_and_Concepts.md
더 자세하게 정리되어 있고, 번역된 문서를 보고싶다면 Github에서 제가 작성한 문서를 확인하세요.

profile
직접 만드는 게 좋은 프론트엔드 개발자

0개의 댓글