본문 바로가기

React

Redux 정리

 Redux 란? 

Redux는 Cross-Component-State 또는 앱 와이드 상태를 위한 상태 관리 시스템으로 애플리케이션을 변경하며 브라우저에 표시하는 데이터를 다수의 컴포넌트나 심지어는 앱 전체에서 관리하도록 도와줍니다.

 

보통 React를 사용하다 보면 많은 상태 관리 hook(useState, useReducer)를 사용하게 되며 실제로 이러한 hook들은 변화하는 데이터를 관리할 수 있게 해 줍니다.

 

예를 들어 사용자가 버튼을 클릭하면 관리하고자 하는 데이터가 변경되고 흔히 UI가 업데이트됩니다.

(useState와 같은 React 상태 관리 hook을 사용하여 어떠한 데이터가 변경되었다고 React에 알립니다.)

 

보통 React의 State는 아래의 세 가지로 구분할 수 있습니다.

 

Local State 

Local State 란 데이터가 변경되어서 하나의 컴포넌트에 속하는 UI에 영향을 미치는 상태입니다.

 

사용자 입력을 받고 useState를 사용해서 사용자의 입력을 모든 키 입력과 함께 State 변수에 저장하거나 버튼을 클릭하면 세부 정보가 표시되고 다시 감춰지는 세부 정보 필드를 on/off 하는 토글 버튼을 예로 들 수 있습니다.

 

보통 useState나 useReducer를 사용해서 컴포넌트 안에서 위와 같은 Local State를 관리하게 됩니다.

 

 

Cross-Component-State

Cross-Component-State 란 데이터가 다수의 컴포넌트에 영향을  미치는 상태입니다.

 

예를 들면 모달 오버레이를 열거나 닫는 버튼이 있고 다수의 컴포넌트에 영향을 미칠 수 있는 모달이 존재하며, 모달을 여는 로직은 모달의 바깥에 존재하고 모달 안쪽에 있는 버튼을 클릭하면 모달을 닫을 수도 있습니다.

 

다수의 컴포넌트가 협력해서 어떠한 모달을 표시하고 감추게 되고 useState나 useReducer를 이용해서 위와 같은 기능을 구현이 가능하며, 이때 prop을 주변에 전달해주어야 하고 흔히 prop 드릴링이 발생하게 됩니다.

 

다수의 컴포넌트에 걸쳐 prop을 전달할 때 여러 컴포넌트를 걸쳐 함수도 전달할 수 있으며, 이때 여러 컴포넌트들은 협력하여 State를 관리하게 됩니다.

 

 

App-Wide-State

App-Wide-State 란 다수의 컴포넌트가 아니라 애플리케이션의 모든 컴포넌트에 영향을 미치는 상태입니다.

 

예를 들어 사용자 인증 기능으로 사용자가 로그인하면 nav bar에 새로운 옵션을 표시하기 때문에 nav bar를 변경해야 할 경우가 있을 수 있으며 뿐만 아니라 많은 컴포넌트에도 더 많은 데이터를 나타나게 한다거나 더 적게 나타나게 한다는 등의 영향을 미치게 될 것입니다. 

 

이때 useState나 useReducer를 사용하여 State 값을 설정해주고 해당 기능을 담고 있는 함수를 업데이트해서 관리할 수 있게 됩니다.

 

 

 

Local State의 경우 하나의 컴포넌트에만 영향을 미치지만 Cross-Component-State와 App-Wide-State의 경우 번거로운 작업이 될 수 도 있기 때문에 React Context라는 React 내장 hook을 사용하면 쉽게 State 관리를 할 수 있게 됩니다.

 

Redux 역시 위와 같은 문제를 해결해줍니다.

 

즉 React Context와 Redux는 Cross-Component-State와 App-Wide-State의 State를 쉽게 관리할 수 있게 도와줍니다.

 

 

 

 Redux 사용 이유 

여기서 다수의 컴포넌트에 영향을 미치는 State를 관리하는 React Context가 이미 있는데 왜 Redux를 사용하는지에 대한 의문이 들 수 있습니다.

 

React Context에는 잠재적인 단점이 몇 가지 존재합니다.

(잠재적이라고 작성한 이유는 단점이 중요하지 않을 수도 있을 수 있기 때문입니다.)

 

잠재적인 단점 중 하나는 React Context를 사용하면 설정이 아주 복잡해질 수도 있고 React Context를 이용한 State 관리가 상당히 복잡해질 수 있다는 점입니다.

 

대형 애플리케이션에서 React Context를 사용한 State 관리

이러한 복잡성은 구축하는 애플리케이션의 크기가 크지 않다면 문제가 되지 않을 수 있지만 애플리케이션의 크기가 크다면에 위의 이지지와 같이 복잡해질 수 있습니다.

 

대형 애플리케이션 구축 시 다양한 Context를 만들어 사용하고 다수의 컴포넌트나 전체 앱에 영향을 미치는 많은 State를 관리하는 형태로

결과적으로 아주 심하게 중첩된 JSX 코드가 나오게 될 것이고 세분화하지 않은 하나의 큰 Context를 만든다면 복잡성이 매우 높은 Context가 될 것입니다.

 

또 다른 잠재적인 단점은 성능입니다.

 

테마를 변경하거나 인증과 같은 저빈도 업데이트에는 React Context가 매우 유용하지만, 데이터가 자주 변경되는 경우에는 좋지 않다는 리액트 팀의 언급이 있었습니다.

 

비록 정확하게 고빈도 업데이트가 무엇을 의미하는지는 말하지 않았지만 보다 자주, 빈번히 발생하는 업데이트의 의미를 담고 있습니다.

 

또한 언급자는 React Context는 유동적인 State 확산을 대체할 수 없다고도 언급하였습니다.

 

하지만 Redux의 경우 유동적인 State 관리가 가능한 라이브러리입니다.

 

 

정리를 해보면 React Context는 복잡한 설정과 관리라는 단점과 잠재적인 선능 이슈가 있으며 Redux는 React Context의 대안이라고 할 수 있습니다.

 

 

 

 Redux 작동방식 

Redux는 애플리케이션에 있는 하나의 중앙 데이터 저장소입니다.

(여기서 data는 State를 가리킵니다.)

 

하나의 중앙 데이터 저장소에 전체 애플리케이션의 모든 상태를 저장하게 되며 인증 상태, 테마, 저장하고자 하는 입력 상태 등 무엇이든 저장할 수 있습니다.

 

Cross-Component-State 나 App-Wide-State 상관없이

 

모든 State를 하나의 저장소에서 관리하게 되면 관리가 어려울 것 같다는 생각을 가질 수도 있는데 다행히 저장소 전체를 항상 직접 관리할 필요가 없습니다.

 

이렇게 중앙 데이터 저장소를 갖게 되면 궁극적으로는 저장소에 데이터를 저장해서 컴포넌트 안에서 저장된 데이터를 사용할 수 있게 됩니다.

 

예를 들어 사용자의 인증 상태가 변경되는 등 저장된 데이터가 변경되면 컴포넌트에서 변경된 데이터를 인지하여 그에 맞는 UI를 업데이트되는 것을 원할 수 있습니다.

 

이 경우 컴포넌트에 데이터 저장소에 대한 Subscription을 설정해주면 테이터가 변경될 때마다 저장소가 컴포넌트에 알려주게 됩니다.

 

 

물론 이 데이터가 State이기 때문에 변경할 방법이 필요합니다.

 

데이터를 변경하는 방법에는 컴포넌트가 절대로 저장된 데이터를 직접 조작하지 않는다는 매우 중요한 규칙 하나가 존재합니다.

 

데이터의 방향이 중앙 데이터 저장소에서 컴포넌트로 단방향 데이터 흐름 규칙입니다.

(컴포넌트에서 중앙 데이터 저장소로 데이터가 흐르지 않습니다.)

 

컴포넌트에서 직접 데이터를 변경하는 방법이 아닌 Reducer 함수를 사용하여 데이터를 변경해줍니다.

 

때문에 데이터를 변경하기 위해서는 Reducer 함수를 설정해주어야 하며 해당 함수는 저장소 안의 데이터의 변경을 담당하게 됩니다.

 

여기서 중요한 점은 데이터를 변경하기 위해 사용되는 Reducer 함수 useReducer hook 과는 다르다는 점입니다.

 

데이터를 변경하기 위해 사용하는 Reducer 함수는 일반적인 개념으로 Reducer 함수는 입력을 받아 해당 입력을 변환하고 줄이는 함수입니다.

 

예를 들어 숫자로 구성된 리스트를 숫자들의 합으로 줄이는 등 입력을 변환해서 새로운 출력(결과)을 반환하게 됩니다.

 

이렇게 설정한 Reducer 함수는 해당 데이터를 Subscription 하는 컴포넌트와 연결해주어야 합니다.

 

컴포넌트에서 Action을 보내고  Reducer 함수는 해당 Action이 수행해야 할 작업을 설명하게 됩니다.

 

Redux는 Action을 Reducer 함수로 전달하고 Action에 대한 수행해야 할 작업 설명을 읽게 됩니다.

 

이어서 Reducer 함수는  Action에 대한 작업을 수행하게 됩니다.

 

컴포넌트에서 Redux로 Action을 전달하고, Redux는 다시 Action을 Reducer 함수에 전달하게 되면 Reducer 함수는 해당 Action에 맞는 작업을 수행하여 새로운 State를 반환하고 반환된 State가 중앙 데이터 저장소에 저장되게 됩니다.

 

이렇게 중앙 데이터 저장소의 State가 업데이트되면 Subscription 하는 컴포넌트가 State가 업데이트되었다고 전달받고 UI를 업데이트할 수 있게 됩니다.

 

 

 

 


 

 

위의 Redux 설명을 간단한 JavaScript 코드와 함께 살펴보도록 하겠습니다.

 

NodeJS를 사용하면 브라우저 밖에서 JavaScript를 실행할 수 있기 때문에 JavaScript 파일을 NodeJS로 실행하겠습니다.

 

npm init -y

우선 npm init을 실행해줍니다.

 

npm i redux

다음으로 redux 라이브러리를 설치해줍니다.

 

JavaScript 파일에서 redux를 import 해줍니다.

 

 

다음으로 해야 할 작업은 앞서 설명한 중앙 데이터 저장소와 Reducer 함수를 만들어 주어야 하고 Action과 컴포넌트도 필요하며 현재 React를 사용하지 않고 있기 때문에 저장소를 Subscription 하기 위한 설정도 필요합니다.

 

우선 중앙 데이터 저장소를 만들어 주도록 하겠습니다.

위와 같이 코드를 작성하면 저장소가 생성되게 됩니다.

 

이렇게 생성된 저장소는 데이터를 관리하게 되는데 관리해야 할 데이터는 Reducer 함수에 의해 결정되게 됩니다.

 

Reducer 함수는 Action을 전달받을 때마다 새로운 State를 반환하게 될 것입니다.

 

또한 위의 코드가 처음 실행될 때 Reducer 함수가 실행되고 기본 Action을 하게 될 것입니다.

 

Reducer 함수가 초기 State를 반환하게 해주어야 한다는 의미입니다.

 

 

 

 

우선 Reducer 함수를 생성해줍니다.

 

Reducer 함수는 표준 JavaScript 함수이지만 Redux 라이브러리에 의해 호출될 것이며 항상 2개의 파라미터를 받게 됩니다.

 

2개의 파라미터 중 하나는 기존 State이고 하나는 전달받는 Action이며, 이렇게 2개의 파라미터를 전달받으면 값을 리턴해주어야만 합니다.(리턴하는 값은 항상 새로운 State 객체입니다.)

 

또한 Reducer 함수는 부가적인 실행을 하는 코드가 존재해서는 안됩니다.

(예를 들어 http 요청을 전송한다거나 무언가를 로컬 저장소에 기록하는 등의 실행)

 

Reducer 함수 Redux가 제공하는 입력을 취하고 예상된 출력물인 새로운 State 객체를 생성하는 순수한 함수여야만 합니다.

(Reducer 함수가 State 객체를 반환한다고 말한 것은 보통 State는 한 개 이상의 값을 가지기 때문이지 반환하는 값은 어떠한 형태든 상관없습니다.)

 

 

 

위에서 설명한 대로 Reducer 함수는 2개의 파라미터(기존 State, Action)를 받게 해 주었고, 항상 함수는 값을 반환해야 하기 때문에 State 객체를 반환하게 해 주었습니다.

(기존 State, 즉 객체 안 counter(프로퍼티)에 접근하여 Reducer 함수가 실행될 때마다 +1 한 값을 반환하게 해 주었습니다. )

 

 

 

이렇게 생성한 Reducer 함수를 중앙 데이터 저장소에 넣어줍니다.

(이유는 저장소에 어떤 Reducer 함수가 저장된 State를 변경하는지 알려주는 것입니다. )

 

 

 

다음으로 Subscription 설정을 해주도록 하겠습니다. (리액트에서 컴포넌트라고 생각하시면 됩니다.)

getState는 생성된 저장소에서 사용할 수 있는 메서드이며 해당 메서드는 저장소의 State가 업데이트 되면 항상 최신의 State를 제공하게 됩니다.
 
그렇게 되면 Subscription 설정하는 함수는 State가 변경될 때마다 트리거될 것입니다.
 
getState 메서드를 통해 State가 업데이트 될 때마다 항상 최신의 상태를 받을 수 있게 됩니다.
 
 
 
 
 
 

다음으로 Redux가 Subscription 설정하는 함수를 인식하도록 하고 State가 변경될 때마다 해당 함수를 실행하라고 알려주는 작업을 해주어야 합니다.

subScribe 메서드는 인수로 Subscription 설정하는 함수를 취하며 Redux는 State가 변경될 때마다 전달받은 함수를 실행하게 됩니다.
 
 
 
 
여기까지하고 실행시키게 되면 아래의 오류가 발생하게 됩니다.
 
 원인은 저장소가 생성 될 때 Redux가 Reducer 함수를 처음 실행한다는 점입니다.
 
이는 Reducer 함수가 처음 실행될 때 Staterk 정의 되어있지 않기 때문입니다.
(초기 State는 없기 때문입니다.)
 
때문에 State에 기본값을 아래와 같이 주어야합니다.

이렇게 실행을 시켜도 콘솔에는 출력이 되지 않는데 이는 아직 어떠한 Action도 전달하지 않았기 때문입니다.

 
 
 
 
마지막으로 Action을 만들고 전달하도록하겠습니다.
 
dispatch 메서드는 Redux에 Action을 전달하는 메서드입니다. 
 
그리고 Action은 JavaScript 객체이며 해당 객체는 식별자 역학을 하는 type 프로퍼티를 가진 JavaScript 객체입니다.
(전달하는 Action에 존재하는 type 프로퍼티로 Reducer 함수에서 type의 값에 해당하는 실행문을 실행하게 됩니다.)
 
실핼하게 되면 위와 같은 결과를 확인할 수 있습니다.
 
위와 같은 결과가 나온 이유는 초기화 시, 즉 저장소가 초기에 생성될 때  Reducer 함수가 실행되어 1이 되었고 Action을 전달하여Reducer 함수가 실행되었기 때문입니다. 
 
 
하지만 Redux를 사용할 때는 Reducer 함수 내부에서 각각의 Action에 맞는 실행문을 실행하는 것이 목표입니다.
때문에  Reducer 함수 내부에 Action으로 오는 객체 내부의 식별자로 지정한 type에 해당하는 실행문을 설정해주어야 합니다.
이렇게 설정해주고 나면 위의 결과를 확인할 수 있습니다.
 
 저장소가 처음 생성될 때 counter는 0으로 설정되고 Action(INCREMENT)가 전달되었을 때 +1된 값을 반환하게 되기 때문입니다.
 
 
추가적 Reducer 함수 내부에 다른 Action 실행문을 추가해주고 Redux에 Action을 전달하도록 하겠습니다.
 

 
 
 
전체 코드
const redux = require("redux");

const counterReducer = (state = { counter: 0 }, action) => {
  if (action.type === "INCREMENT") {
    return {
      counter: state.counter + 1,
    };
  }

  if (action.type === "DECREMENT") {
    return {
      counter: state.counter - 1,
    };
  }

  return state;
};

const store = redux.createStore(counterReducer);

const counterSubscriber = () => {
  const latestState = store.getState();
  console.log(latestState);
};

store.subscribe(counterSubscriber);

store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });
 

 

정리
1. Redux 중앙 데이터 저장소를 createStore를 사용하여 생성해주었고 Reducer 함수를 생성해주었습니다.
2. 생성된 중앙 데이터 저장소는 저장된 State를 변경할 Reducer 함수를 전달 받았습니다.
3. Subscription 설정하는 함수를 만들어 주었고 함수 내부에서 getState 메서드를 사용하여 저장소에 저장된 State가 변경되면 최신의 State를 받을 수 있게 해주었습니다.
3. Redux가 저장소에 저장된 State가 변경될 때마다 Subscription 설정하는 함수를 실행시키도록 하기위해 subScribe 메서드를 사용하여 설정해주었습니다.

4. Redux에 Action을 dispatch 메서드를 사용하여 전달해주었습니다.

5. Reducer 함수 내부에 각 Action에 해당하는 실행문을 설정해주었습니다.

 

 

 

 

 

Redux를 React에 적용하는 방법

 

 

Redux React에 적용하기

앞서 게시한 Redux 정리를 기반으로 React에 적용하는 방법을 정리하도록 하겠습니다. Redux 정리 Redux 란? Redux는 Cross-Component-State 또는 앱 와이드 상태를 위한 상태 관리 시스템으로 애플리케이션을

nicehyun12.tistory.com

 

 

 

 

 

 

'React' 카테고리의 다른 글

Infinite Scroll & pagination API  (0) 2022.11.29
Redux의 State를 올바르게 사용하는 방법  (0) 2022.11.08
사용자 입력값 유효성 검사  (0) 2022.11.01
Custom Hooks  (0) 2022.10.23
React Routing  (0) 2022.10.18