[Redux] 리덕스란 무엇인가?

SuamKang·2023년 8월 7일

Redux

목록 보기
1/6

리엑트 개발을 연습해오면서 컴포넌트도 중요한 개념이지만 동시에 많아지는 상태를 어떻게 효율적으로 관리해야하는지도 리엑트 개발자가 되려면 매우 고민하고 신경써야하는 부분인것 같다.



내가 그동안 관리 했던 메커니즘은 리엑트에서 제공하는 ContextAPI를 사용하여 전역에서 관리를 하며 상태를 업데이트 하곤 했다.

하지만 이보다 상태관리 라이브러리인 리덕스를 굉장히 많이 사용하는 이유가 무엇일까?
한번 자세히 알아보도록 하자!


Redux??

그럼 리덕스가 뭘까?

리덕스(Redux)

상태관리 라이브러리로써, 예측 가능한 상태 관리를 도와주고 자바스크립트 애플리케이션의 복잡한 상태를 중앙 집중화하고 관리하기 위해 설계되었다.

상태, 즉 애플리케이션을 변경하고 화면에 표시하는 데이터를 관리하도록 해서 그런 데이터들을 다수의 컴포넌트나 심지어는 앱 전체에서 관리하도록 도와주는 시스템이다.


state의 종류와 구분


리엑트에서 다루는 상태는 3가지로 구분할 수 있다.

1. Local State(로컬상태)

: 데이터가 변경되어서 하나의 컴포넌트에 속하는 UI에 영향을 미치는 state를 말한다. 즉, 컴포넌트 내에서만 관리되는 상태를 의미합니다. 로컬 상태는 주로 useState 훅을, 약간 복잡하면 useReducer 훅을 사용하여 관리한다.

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const increaseHandler = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increaseHanlder}>Increment</button>
    </div>
  );
}

위 예시처럼 버튼의 이벤트를 받아 상태를 업데이트 하고, 표시되는 조건에 필드(JSX코드)를 토글형태로 받아볼때 처럼 해당 컴포넌트 안에서 관리되는 상태이다.


2. Cross-Component State(컴포넌트 간 공유상태)

: 여러 컴포넌트 간에 공유되는 상태를 의미한다. 이것도 마찬가지로 useState나 useReducer훅을 이용해서 구현할 수 있고 중요한점은 이러한 상태들이 하위 컴포넌트들 간에 전달되어야 하며, 주로 부모 컴포넌트에서 관리된다. 이 상태들은 prop이 되며, prop체인(prop drilling)을 구축해야한다. 이를 통해 다수의 컴포넌트 간에 데이터(상태)를 전달하고 공유할 수 있게된다.

예를 들면 모달 컴포넌트 같은 경우, 여는 트리거함수는 부모컴포넌트에 있고 닫는 트리거함수는 모달 안쪽에 버튼으로 자리하고 있으니 그런 경우엔 서로 공유되는 상태들이 존재할 것이다.

ex) 모달

import React, { useState } from "react";

function App() {
  const [isOpen, setIsOpen] = useState(false);

  const openModalHandler = () => {
    setIsOpen(true);
  };

  const closeModalHandler = () => {
    setIsOpen(false);
  };

  return (
    <div>
      {isOpen && <Modal onClose={closeModalHandler} />}
      <button onClick={openModalHandler}></button>
    </div>
  );
}

function Modal(props) {
  return (
    <div>
      <p>모달을 닫으시겠습니까?</p>
      <button onClick={props.onClose}>Close</button>
    </div>
  );
}

ex) 메시지 입력

import React, { useState } from 'react';

function ParentComponent() {
  const [message, setMessage] = useState('');

  const handleMessageChange = (newMessage) => {
    setMessage(newMessage);
  };

  return (
    <div>
      <ChildComponent message={message} onMessageChange={handleMessageChange} />
    </div>
  );
}

function ChildComponent(props) {
  const handleInputChange = (event) => {
    props.onMessageChange(event.target.value);
  };

  return (
    <div>
      <input type="text" value={props.message} onChange={handleInputChange} />
      <p>Message: {props.message}</p>
    </div>
  );
}

3. App-Wide State(전역 앱 상태)

: 애플리케이션 전체에서 공유되는 상태를 의미한다. 여러 컴포넌트들이 이 상태에 접근하여 사용할 수 있으며, 주로 리덕스나 Context API를 사용하여 전역 상태를 관리할 수 있다.

ex)

import React, { createContext, useContext, useState } from 'react';

const AppContext = createContext();

function App() {
  const [username, setUsername] = useState('');
  
  return (
    <AppContext.Provider value={{ username, setUsername }}>
      <Header />
      <MainContent />
    </AppContext.Provider>
  );
}

function Header() {
  const { username } = useContext(AppContext);

  return <header>Welcome, {username}!</header>;
}

function MainContent() {
  const { username, setUsername } = useContext(AppContext);

  const handleUsernameChange = (event) => {
    setUsername(event.target.value);
  };

  return (
    <div>
      <p>Username: {username}</p>
      <input type="text" value={username} onChange={handleUsernameChange} />
    </div>
  );
}

보통 전역으로 상태를 관리하는 경우엔 사용자 인증과같이 로그인 여부에 따라 UI가 변경되거나 새로운 옵션을 부여해야 할 수 있다.

이렇게 전역으로 상태를 사용하고 관리할때 물론 props로 함수를 업데이트 할 수 있지만, 번거로울 수 있다.그래서 이를 위 예시와 같이 ContextAPI로 관리해 준것이지만,
리덕스도 같은 문제를 해결해 줄 수 있다는것이다!



Redux vs ContextAPI


물론 컨텍스트와 리덕스 메커니즘을 둘다 혼합하여 앱의 환경에 맞게 사용할 수 도 있다. 하지만 그래도
왜 전역상태관리를 컨텍스트보다 리덕스를 더 많이 사용할까?


ContextAPI의 잠재적인 단점

그렇다. 몇가지 단점이 존재했다.


1. Complex Setup / Management(많은 컴포넌트와 내용이 있는 애플리케이션의 경우 상당히 복잡해진다.)
: 다뤄야하는 컨텍스트가 많아지고 적용을 하게된다면
이런 코드가 나타난다.

return (
  <AuthContextProvider>
    <ThemeContextProvider>
      <UIInteractionContextProvider>
        <MultiStepFormContextProvider>
          <UserRegister />
        </MultiStepFormContextProvider>
      </UIInteractionContextProvider>
    </ThemeContextProvider>
  </AuthContextProvider>
)

보다시피 아주 많고 다양한 컨텍스트가 중첩된 JSX코드가 설정된다.
물론, 하나의 컨텍스트에 많은 상태를 관리해서 적용할 수도 있지만 그렇게되면 그 자체가 유지하고 관리하기가 어려워 질수 있다.(인증, 테마, 사용자입력, 모달 등등 이러한 상태를 한군데에서?? nono..)


2. Performance(성능)

: 그다음은 성능이다.

위 트윗은 리엑트 개발 팀원이 공식적으로 언급한 내용인데, 컨텍스트API를 도입된 당시에 저빈도 업데이트(테마변경, 인증 등등)같은 업데이트는 훌륭하지만, 데이터가 자주 변경되는 경우에는 좋지 않다고 언급한걸 볼 수 있다.


또한 유동적인 상태 확산 을 대체할 수 없다고도 언급했다.
리덕스는 유동적인 상태관리 라이브러리이다.
따라서 컨텍스트가 리덕스를 유연하게 대체할 수 없고, 한계점이 분명히 있기때문에 여러 리엑트 개발자들이 사용하고 있는것 같다는 생각이다.

요약

요약해보자면 리덕스는 리엑트가 제공하는 내장된 전역상태관리 시스템인 ContextAPI의 설정과 관리의 한계로 인해 대안으로 도입된 시스템이라고 보면 될것같다!!



Redux의 작동방식


✔️ Store


먼저, 리덕스는 하나의 중앙 데이터 저장소이다.

여기서 데이터는 상태를 가리키고 매우 중요한개념이다.
절대로 2개이상 갖지않으며 한개의 저장소를 갖는다. 이 저장소는 Store라고 불린다.

그리고 이 저장소에 모든 상태들을 저장한다.(상태 바구니)

궁극적으로 이 데이터들을 저장해서 컴포넌트 안에서 이를 사용할 수 있다.
그리고 이 컴포넌트를 위해 Store에대한 Subscription을 설정한다.

컴포넌트가 저장소를 구독하면, 데이터가 변경될 때마다 저장소가 컴포넌트에 알려주게 되는것이다.
그럼 그 컴포넌트는 필요한 데이터를 받을 수 있다.
예를 들면 현재 사용자의 인증상태같을걸 받는것이다.


하지만, 데이터는 상태이기때문에 때때로 변경된다.
그럼 이 변경은 어떻게 다뤄야 하는걸까?


변경할 데이터는 절대로 반대방향으로 흐르지 않는다. 또한 컴포넌트에서 직접적으로 조작하지 않고,
Reducer라는 개념을 이용하게 된다.

✔️ Reducer


Redcuer함수는 저장소의 데이터를 변경하는 역할을 해준다.
useRedcuer 훅에서 다룬것과 비슷하다.
리듀서 함수는 일반적인 개념으로, 입력을 받아서 그 입력을 변환하고 새로운 출력/결과를 뱉어낸다.

예를들면, 숫자로 된 리스트를 그 숫자들의 합으로 줄일 수 있게되는것 처럼 말이다.

그럼 이 리듀서를 통해 새로운 출력(변경된 데이터/상태)을 뱉어내기 위해서 변경을 트리거 할 수 있도록 구독된 컴포넌트에 무언가를 설정해주어야한다.


✔️ Action


바로 이 action이다.
리덕스의 3번째 개념으로 저장소가 구독하고 있는 컴포넌트에서 action을 발송(dispatching)한다.
즉, 컴포넌트가 어떤 action을 트리거 한다고 볼 수 도 있다.

그리고 해당 action은 자바스크립트 객체이며 그게 reducer가 수행해야할 작업을 설명해준다.


✔️ Dispatch


그래서 리덕스는 해당 action을 reducer로 전달해야하는데 이 action을 트리거 해주는 메소드가 바로 dispatch이다.

이렇게 reducer에게 action을 전달하게 되면 action의 type에 따라 새로운 상태를 뱉어내고 그게 중앙 데이터 저장소인 store의 기존 상태를 대체하게 된다.

그리고 그러한 과정이 일어나서 store의 상태가 업데이트되면 구독중인 컴포넌트가 알림을 받게 되고 컴포넌트는 최종적으로 그 상태에 따른 UI업데이트를 할 수 있게 되는것이다.

Redux Flow

core redux concept
사진출처: Udemy



정리


리덕스의 주요 개념


  1. Store : 애플리케이션의 상태를 저장하는 중앙 저장소. 스토어는 리덕스의 핵심 요소이며, 애플리케이션의 모든 상태 정보가 하나의 스토어 안에 저장된다.

  2. Action: 상태 변경을 나타내는 객체로, 어떤 일이 일어났는지 설명한다. 액션은 type 필드를 필수적으로 갖고 있으며, 추가적인 데이터를 포함할 수 있다.

  3. Reducer : 액션을 받아서 이전 상태와 함께 새로운 상태를 반환하는 순수 함수이다. 리듀서는 상태의 변화를 처리하고, 여러 리듀서를 결합하여 더 큰 상태 트리를 구성할 수 있다.

  4. Dispatch : 액션을 스토어로 보내 상태 변경을 트리거하는 메소드이다. 디스패치를 통해 액션을 스토어로 전달하면, 리덕스는 액션을 처리하고 리듀서에게 액션을 전달하여 새로운 상태를 생성한다.

  5. Subscribe : 스토어의 상태 변화를 감지하고, 변화가 일어날 때마다 특정 함수를 호출하여 업데이트를 처리하는 메커니즘이다.

  6. Middleware : 액션과 리듀서 사이에서 동작하는 확장 기능이다. 미들웨어를 사용하여 비동기 작업을 처리하거나 액션을 가로채 수정할 수 있다. 대표적인 예로 Redux Thunk, Redux Saga, Redux Observable 등이 있다.


리덕스의 기본 작동 Flow


  1. 컴포넌트에서 dispatch를 통해 action을 보낸다.
  2. store는 받은 Action을 Reducer에게 전달한다.
  3. Reducer는 이전 state와 action을 기반으로 새로운 state를 반환한다.
  4. store는 새로운 상태를 갱신하고, subscribe된 컴포넌트들에게 상태 변경을 알린다.
  5. subscribe된 컴포넌트들은 새로운 상태를 받아 렌더링한다.
profile
마라토너같은 개발자가 되어보자

1개의 댓글

comment-user-thumbnail
2023년 8월 7일

즐겁게 읽었습니다. 유용한 정보 감사합니다.

답글 달기