React와 Redux (1)

yeong·2022년 6월 17일
0

react

목록 보기
1/4
post-thumbnail

react와 redux

redux를 도입하자..

여태까지 개발을 하며 사실 redux를 반드시 써야한다! 라는 경험은 사실 드물었다. 전역으로 상태 관리를 해야하는 경우 대부분 contextAPI로 해결되었기때문이다.
사실 사용하면서도 빈번한 렌더링때문에 걱정이 된 적도 있었으나ㅎㅎ.. 전역 상태 관리 로직을 모두 바꿀만큼의 크리티컬한 케이스를 만난적이 없기때문에 잘 버텨왔다...
그런데, 이번 프로젝트를 진행하며 각각 다른 위치에서 호출되는 A,B라는 컴포넌트가 있고 A컴포넌트에서 api를 호출하면 B컴포넌트에 로딩바를 띄워야 하는 상황이 되었다.
사실 context로 처리하면 매우 간단하나, 로딩상태는 어디에서나 사용될 수 있고 전역에서, 많은 컴포넌트에서 사용될 것이다. 이러한 경우 context보다는 redux로 상태를 관리하는것이 더 좋겠다 싶어 redux를 적용하려고 한다! 그리고 필요할때마다 다시 확인하기 위해 블로그에 기록을 남긴다

redux란?

우선 redux가 무엇인지 간단하게 정리하자.
redux는 크로스 컴포넌트 또는 app wide 상태를 위한 상태 관리 시스템이다. 쉽게 전역에서 상태를 관리할수 있도록 도와주는 아이이다.
상태는 크게 3가지로 분리된다. 로컬, 크로스 컴포넌트 , 그리고 앱 와이드
로컬은 말 그대로 해당 컴포넌트 안에서만 관리되는 상태이다.
크로스 컴포넌트와 앱와이드는 그 범위는 다르지만 모두 다수의 컴포넌트에 영향을 미치는 상태를 가리킨다. 일반적으로 props chaining , drilling을 통해 구축이 가능하나 contextAPI, redux와 같은 라이브러리를 사용해서 관리할 수도 있다.

사실 context는 이미 react에 내장되어있다. 그렇다면 왜 굳이 redux를 설치해서 사용하는걸끼?

왜 redux?

context도 매우 유용한 기능이다. 그러나 context는 잠재적인 단점이 있다.

  1. 설정과 관리가 매우 복잡해질 수 있다. 작은 앱이라면 괜찮으나 규모가 큰 앱에서는 여러개의 컨텍스트가 사용될때, 과도한 중첩등 복잡한 구조가 될 수 있다.
  2. 성능이 떨어진다.
    실제로 react팀원이 contextAPI에 대해 데이터가 너무 자주 바뀌는 경우 context가 적합하지 않다고 언급한적이 있다. context는 해당 하나의 저장소에서 단 하나의 상태라도 구독하고 있는 모든 컴포넌트가 재평가되도록 한다. 즉 다른 상태가 업데이트 되더라도 리렌더링이 발생한다.
    반면 redux는 특정 상태를 구독하고 있는 컴포넌트만 리렌더링한다. (단 객체타입이 아닌경우)

사실 요즘 redux외에도 수많은 상태관리 라이브러리들이 등장하고 있다. 다른 라이브러리에 대해서도 공부가 많이 필요하다ㅠㅠ..

redux의 작동방식

저장소

사실 redux에는 많은 요소가 존재하기때문에 처음 redux를 배울때 가장 어려웠던점이 이 동작원리이다.
redux는 하나의 중앙 데이터 저장소이다. 여기서 데이터는 상태를 가리킨다. 즉 redux는 하나의 상태 저장소이다.
모든 상태가 하나의 저장소에 저장된다.

데이터 읽기

우리는 이 저장소에서 상태를 가져다가 컴포넌트에서 사용할 수 있다.
정확히는 저장된 상태가 변경되면 컴포넌트에서 그것을 인지하고 컴포넌트를 재평가할 수 있다. 단, 컴포넌트가 저장소가 변경되었다는 것을 알기 위해서는 저장소를 구독해야하며 구독 후에 저장소는 데이터가 변경될때 컴포넌트에게 알려주고 컴포넌트는 변경된 상태를 전달받는다.

데이터 수정

단순히 상태를 전달받는것 외에도 상태를 업데이트하는 기능도 필요하다. 여기서 중요한것은 컴포넌트는 저장소를 직접적으로 수정할 수 없다. 즉 데이터는 [저장소 => 컴포넌트] 의 방향으로만 흐르며 [컴포넌트=>저장소]는 불가능하다.
대신 컴포넌트는 reducer라는 것을 사용해 상태를 수정을 요청할 수 있다.
reducer함수는 상태를 변환하는 역할을 한다.

그렇다면 컴포넌트는 어떻게 reducer에서 수정을 요청할 수 있을까?
컴포넌트는 reducer에서 수정을 요청하기 위해 action을 사용한다. action을 reducer로 보내서 "이러이러한 action을 수행해줘!" 라고 전달하는 것이다. 여기서 전달이라는 개념은 redux에서 dispatch라는 용어로 사용된다. 즉 component가 action을 reducer로 dispatch하는 것이다.

이제 reducer가 action을 받게 되면 action에 담긴 type과 data에 따라 새로운 상태를 내뱉는다. 그리고 reducer가 내뱉은 이 상태는 그대로 store의 기존 상태를 대체한다. 이제 저장소의 상태가 업데이트 되었으니 이 저장소를 구독중인 컴포넌트는 자신이 요청한대로 변경된 상태를 전달받을 것이다.

기초 설정

우선 redux 라이브러리를 설치해야한다.
react에서 redux를 조금 더 편하기 사용할 수 있도록 도와주는 라이브러리인 react-redux도 같이 설치한다.

npm install redux react-redux

이제 저장소를 만들 폴더를 지정한다

src 
└─store
    ├─ index.js
    └─ [reducer].js

우선 간단하게 위와 같은 구조로 작성하였다.
action들을 모아두는 actions폴더를 따로 정의하기도 하지만 우선 reducer를 작성할때 참고하기 위해 같은 파일내에 작업할 예정이다.

store 만들기

가장 먼저 상태 저장소가 될 store를 생성한다. store은 redux의 createStore라는 메서드를 통해 생성이 가능하다.
store을 생성하면 이 저장소를 구독할 컴포넌트들이 store에 접근할 수 있도록 해주어야하는데 이때 방식은 context와 유사하다. react-redux의 요소 provider로 store에 접근할 모든 하위 컴포넌트를 wrap함으로서 하위 컴포넌트들이 store에 접근할 수 있도록 해주어야 한다.
그리고 react-redux가 제공하는 Provider는 redux가 생성한 store에 대한 정보가 없으므로 속성값으로 이를 전달한다.

import { Provider } from "react-redux";
import store from "./store/index";
function App() {
  return (
    <div className="App">
      <Provider store={store}>
        <Layout>
       		 ...
        </Layout>
      </Provider>
    </div>
  );
}

초기상태, reducer , action 생성자 만들기

다음으로 reducer와 action 생성자를 만든다.
action 생성자는 단순하게 객체타입의 액션을 만들어주는 함수이다. 액션은 type이라는 속성을 갖는 일반 객체로서, 액션 생성자를 만들지 않고 바로 type속성을 갖는 객체를 dispatch하여도된다.

reducer는 store폴더 하위에 별도의 reducer.js파일을 만들어서 작성한다. store와 같은 파일에서 관리하여도 되지만 reducer가 여러개가 되는 경우 파일이 과도하게 길어질 수 있다.
reducer는 자동으로 state와 action을 매개변수로 전달받는다. 또한 새로운 상태 객체를 반환해야한다.

초기상태는 api호출 상태를 관리할 것이므로 상태 객체에 status 속성과 error 메시지를 담을 속성을 지정한다.

초기상태

const initialStatusState = {
  status: "",
  error: null,
};

api호출 상태를 변경하는 액션을 생성하는 changeStatus 생성자를 생성한다
액션생성자

export function changeStatus(data) {
  return {
    type: "CHANGE_STATUS",
    data: data,
  };
}

리듀서 함수는 action을 받아 새로운 상태와 error를 반환하도록 한다.

리듀서

const statusReducer = (state = initialStatusState, action) => {
  switch (action?.type) {
    case SUCCESS:
      return {
        ...state,
        status: SUCCESS,
        error: null,
      };
    case ERROR:
      return {
        ...state,
        status: ERROR,
        error: action.data.error,
      };

    default:
      return state;
  }
};

reducer와 store를 모두 작성했다면 둘을 연결시켜야한다. store에 이 reducer에서 상태값을 가져와! 라고 연결을 해주는 과정이다.

이제 초기 설정은 완료되었다!
추가로 상태를 구독할 컴포넌트에서 store를 구독하고 action을 dispatch하도록 설정해야한다.
해당내용은 2편에서 이어서 작성하겠다.

ref :
React 완벽 가이드 with Redux, Next.js, TypeScript
벨로퍼트와 함께하는 모던 리액트

profile
안녕하세요!

0개의 댓글