<근본 시리즈 Redux. 1탄. 사용 이유와 핵심 5 개념.>

강민수·2022년 5월 8일
0

근본 시리즈

목록 보기
5/7
post-thumbnail

이번에는 필자가 최근에 관심을 두고, 사용해 본 리덕스에 대한 시리즈를 시작하고자 한다.

프론트 엔드 개발자라면, 굳이 리덕스가 아니더라도 상태 관리형 라이브러리를 쓸 수밖에 없다. 그 중에서도 리액트의 사용자 수에 비례해서 리덕스 역시 이제는 수많은 개발팀에서 사용하고 있다. 그렇다면, 왜 사용하는 것인지? 그리고 어떤 방식으로 동작하는 것인지에 대해 한 번 알고 넘어갈 필요가 있다. 그래서 이번 근본 시리즈로 리덕스를 준비했다.

1. Why Redux?

먼저, 리덕스는 왜 쓸까? 어떤 기술이든지, 뭔가 탄생한 배경이 있을 것이다. 흔히들 리액트에서 리덕스를 많이 쓰니까, 많이 쓴다고 생각하는 사람도 있는데, 반은 맞고 반은 틀린 얘기다.

물론, 리액트와의 접합성이 굉장히 좋아서 많이 쓰는 것은 맞다.

하지만.... 사실 리덕스는 리액트의 부속품이나 주종 관계가 절대 아니다!!!

리덕스는 리액트 뿐만 아니라, 어떤 프레임워크 라이브러리 심지어는 바닐라 js에도 적용해서 쓸 수 있는 독자적인 상태 관리 라이브러리다.

1) 기존의 상태 관리.

그러면, 먼저 기존의 상태 관리가 어떤 식으로 이뤄졌는 지부터 알고 넘어가는 것이 좋다. 여기서는 일단 리덕스와 가장 궁합이 좋은 리액트를 기반으로 설명 드리겠다.

리액트의 컴포넌트의 상태 값은 기본적으로 프롭스와 스테이트로 이뤄지는 것은 리액트를 써 본 개발자라면 누구나 알 것이다. 상태 관리는 다음 그림과 같다.

즉, 최상단의 부모 컴포넌트가 전체 상태와 관련된 프롭스를 모두 가지고 있다. 이 프롭스는 다른 자식 컴포넌트 들에서는 바뀔 수 없다. 이게 단방향 데이터 바인딩 흐름인 리액트의 구조이기 때문인데, 이로 인해 디버깅이나 데이터의 흐름은 상대적으로 파악하기 쉽다. 하지만, 문제는 이 컴포넌트 구조가 단층 구조일 때는 상관 없지만, 깊어지면 깊어질수록 문제가 생긴다. 즉, 프롭스를 끊임 없이 아래로 내려줘야만 한다. 프롭스 뿐만이 아니라, 스테이트 역시 물론 컴포넌트 내부에서 상태 값을 관리하는 내부 구조이지만, 간혹 자식 컴포넌트에 내려줘야 하는 상황도 생긴다. 이때 스테이트 역시 결국은 프롭스로 처리해 줘야만 한다.

이런 구조에 대해, 필자뿐만 아니라, 수많은 개발자 들 역시 불편함을 느꼈다. 이 불편함이라는 것이 진짜로 공감을 할 수 있어야만 이 기술의 참된 뜻을 알고 쓸 수 있다고 생각한다. 그래서 이런 불편함과 함께 리덕스를 쓰는 활용 법이 대두되기 시작했다.

2) 그렇다면, 리덕스는 어떤데?

자~ 아래 그림을 다시 한 번 살펴보자.

바로 구글에 without Redux라고 치면 나오는 대표적인 예시 이미지다. 이 그림이 모든 것을 담고 있다고 해도 과언이 아니다. 지금은 막상 이렇게만 접근하면 정확하지 않을 수 있으니, 한 번 천천히 잘 살펴봅시다..

일단, 간략하게만 던지자면, 기본적인 프롭스 처리의 구조가 왼쪽의 그림처럼 내려주고 받고, 또 보내고 이럴 필요가 없어진다. 즉, 리덕스는 하나의 스토어에서 모든 데이터를 다 가지고 있다. 그래서 기존의 리액트 구조로 따지면, 부모 컴포넌트가 데이터를 다 가지고 있는 것과 같은 구조다. 따라서 이 스토어가 데이터가 필요한 컴포넌트에게 뿌려준다. 따라서 리액트의 단방향 데이터 흐름 구조인 flux 구조는 잘 따르면서 굳이 불 필요한 프롭스 상속이 필요 없게 된다.

2. 리덕스 필수 개념 정리.

리덕스를 사용하는 것에 대해서 이제는 이해 했을 것이라고 본다. 물론, 사실 필자 포함, 이 기술이 정말 진정으로 왜 쓰는 지에 대해서 공감하고 싶다면, 한 번 작은 단위라도 리덕스를 사용할 때와 하지 않을 때로 나눠서 직접 프로그래밍을 해보는 것이 가장 이해하기 쉽다. 여하튼, 이제는 리덕스에서 사용하는 필수 개념들에 대해 한 번 짚고 넘어가 봅시다.

먼저, 아래 그림을 잘 살펴보자.

이 그림은 리덕스 공식 문서에 나와 있는 데이터 흐름 구조다. 간단히 설명하자면, 스토어라는 전역 상태 저장소에 스테이트를 사용하는 컴포넌트에 내려준다. 만약, 이 컴포넌트에서 변경된 상태 값을 사용하고 싶다면, 디스패치라는 새로운 함수를 호출해 줘야만 한다. 그리고 이 dispatch는 다시 전역 스토어에 지정되어 있는 액션을 부르게 되고, 이에 따라 리듀서 함수가 작동한다. 이후, 변경된 스테이트 값은 다시 컴포넌트 단으로 전달이 되고, 변경된 스테이트 값에 따라 컴포넌트의 ui도 바뀌는 구조다. 자~. 그러면 이렇게만 봐서는 제대로 알 수가 없으니, 다시 한 번 하나씩 용어 정리를 해보겠다.

1) store.

스토어는 말그대로, 영문명 그대로, 우리가 아는 그 가게가 맞다.

왜냐하면, 이전에 언급해었듯이 하나의 큰 저장소이기 때문이다. 이 저장소에는 없는 것이 없다. 따라서 필요할 때면, 언제든 가져다가 쓸 수 있는 것이다. 물론, 가끔 보면 가격이 바뀐다던지, 행사 상품이라서 2+1, 1+1과 같이 값이 변동될 때도 있다. 그럴 때 가게 주인은 그 값을 변경해 주고, 손님은 변경된 가격 혹은 상품을 가지고 계산대로 가져간다. 이처럼 스토어는 이런 모든 우리 어플리케이션의 상태 값을 가지고 있고 관리하고 있는 슈퍼마켓이다.

2)reducer.

다음은 리듀서다. 리듀서는 스토어가 마트 그 자체라면, 마트 계산대 직원과 같다고 생각하면 이해가 쉽다. 즉, 손님인 컴포넌트는 어떤 제품인 스테이트를 골라서 그 가게에 나가기 전에, 계산을 꼭 하고 나가야만 한다. 일종의 처리인 셈인데, 이 처리를 리덕스에서는 리듀서라는 대표 함수가 처리해 준다. 그래서 리듀서는 데이터를 수정해 주고, 수정된 데이터를 컴포넌트에게 전달해서 리턴시켜 주는 함수다. 그렇다면, 이 값이 그냥 바뀔까? 아니다. 분명 원칙이 있어야 하지 않을까? 그럴 때 필요한 것이 바로 action이라는 것이다.

3) 액션 생성자.

리덕스에서는 액션 생성자라는 특징이 있는 녀석이 존재한다. 이 녀석은 아까 말씀드린 것처럼, 스테이트의 모든 변경 사항과 상호작용이 거쳐가야 하는 액션의 생성을 담당한다. 즉, 언제든 애플리케이션의 상태를 변경하거나 뷰를 업데이트하고 싶다면 액션을 생성해야만 한다. 액션 생성자는 타입(type)과 페이로드(payload)를 포함한 액션을 생성한다. 타입은 시스템에 정의 된 액션들(일반적으로 상수들) 중의 하나이다. 코드로 나타내 보면, 다음과 같다. 물론 형태는 다양하게 개발자가 정의내릴 수 있으므로, 꼭 고정된 형식이 있는 것은 아니다. 하지만, 대체로 이런 식으로 어떤 타입인지 가리키는 타입과 그 안에 인자들이 쓰이는 지 명시해 준다. 물론 필자는 현재 타입 스크립트 기반으로 만들었으므로, 다음처럼 타입 지정을 해 줬다. 이 지정된 타입에 담기는 인자들을 정의해 주고, 그 인자들을 리듀서에 넘겨주는 역할을 하는 것이 액션이라고 생각하자.

type CounterAction =
  | {
      type: 'CORRECT'
      currentIndex: number
      correctCount: number
      isCorrect: boolean
      selected: string
    }
  | {
      type: 'INCORRECT'
      currentIndex: number
      incorrectCount: number
      isCorrect: boolean
      selected: string
    }

즉, 바코드와 같은 역할이라고 생가가하면 이해가 더 쉽겠다.

우리는 이 바코드를 통해 어떤 값이 들어 있는 지 확인하고, 계산대에서 값을 찍어 준다. 따라서 액션은 리듀서가 그 값을 어떤 식으로 처리할 지 결정해 준다.

4) Dispatch.

그렇다면, 여기서 약간 의문이 드는 분들도 있으실 것이다.

"그러면. 컴포넌트에서 만약 어떤 이벤트를 발생시켜서 스테이트 값을 바꾼다면, 어떻게 스토어에 전달하죠?"

좋은 질문이다. 그게 바로. Dispatch가 하는 역할이다. 즉, Dispatch를 통해서 이벤트를 흔히 스토어에 전파시키는 것이다. 보통은 디스패치에는 액션의 타입을 지정해서 보낸다. 즉, 디스패치가 어떤 액션을 부르면, 그 액션에 연결된 리듀서가 반응을 하고, 리듀서는 그 반응에 따라 미리 본인 내부에 정의된 스테이트 변경 값을 반환하는 것이다.

5. Subscribe.

"오잉? 그러면, 컴포넌트는 변화된 값을 어떻게 또 감지해서 화면에 보여줘요?"

그렇다. 이 역할을 하는 것이 또 있다. 바로 Subscribe라고 하는 구독이다. 딱 구독한다는 개념이 맞는 것이, 이 구독을 통해 각각의 컴포넌트는 스토어 내부의 스테이트 값의 변화를 감지할 수 있는 것이다. 물론 이 서브스크라이브는 많이 생략도 되고 있는 추세이기는 하다. 왜냐하면, 리액트의 경우, react-redux에서 자체적으로 useselector라는 훅을 만들어서 각각의 컴포넌트가 스토어의 스테이트 값을 가져올 수 있도록 하기 때문이다. 하지만, 기본 리덕스의 개념으로는 충분히 알고 넘어가는 것이 좋다.

앞선 리덕스의 개념을 전반적으로 정리해 보면, 다음과 같은 벤다이어그램 구조다.

3. 이제 실전으로.

일단, 1편에서는 리덕스가 무엇이고 왜 사용하는 지, 그리고 핵심 개념 위주로 살펴봤다. 아직 이해가 덜 되었을 수도 있고, 감이 안 잡힐 수도 있다. 그래서 다음 편에서는 직접 코드 상으로 구현하는 방법과 더불어 어떤 식으로 활용하는 것인지에 대해 적어보겠다. 그러면 다음 편에서 찾아뵙겠다.

profile
개발도 예능처럼 재미지게~

0개의 댓글