brunch

You can make anything
by writing

C.S.Lewis

by zwoo Mar 28. 2021

리덕스로 전역상태 관리하는 연습

1. 리듀서와 디스패치라는 용어에 친숙해지기

리액트 프로젝트에서는 상태관리가 중요하다. 정적인 페이지를 보여주는 기술에 지나지 않았던 웹사이트가 최근 몇년간 동적으로 동작하는 애플리케이션으로 완전히 변모하면서 리액트라는 기술이 등장한 것이기 때문이다. 리액트 프로젝트를 하면서 상태관리를 엉망으로 한다는 것은 이 라이브러리를 100퍼센트 활용하지 못하는 것이다. 우리가 선언한 변수들은 상태를 가지고 있고, 한 컴포넌트 내에서만 변경될 수도 있지만 때로는 멀리 떨어진 또다른 컴포넌트에도 변경사항을 알려주어야 할 수도 있다. 가령 좋아요 버튼을 클릭하는 액션은 당장 이 페이지 내에서는 'isLiked' 변수의 상태를 'false' 에서 'true'로 변경시키고, 그와 동시에 당장 눈에 보이지 않는 마이페이지 컴포넌트에도 이 변경사항을 알려주어 사용자가 좋아요를 누른 리스트에 이 아이템을 추가해야 한다.


이렇게 멀리 떨어진 곳까지 상태변경을 알려주는 일을 여러 단계를 거치지 않고 한 단계로 처리하면서, 혹시나 빠트리고 전달하거나 실수로 잘못 전달되는 일이 없도록 하겠다는 두가지 목적을 위해 전역상태 관리방법들이 생겨났다. 리액트에서는 자체적으로 createContext API 를 제공하고 있고, 프레임워크에 구애받지 않고 사용할 수 있는 크로스 플랫폼 라이브러리인 Redux 도 있다. 리액트 프로젝트에서는 전역상태 관리방법으로 context 또는 redux 중에서 선택할 수 있다는 뜻이다. 그동안의 프로젝트에서, 나는 비록 완전히 이해하지는 못했지만 알고 있는 선에서 context 를 사용해 전역상태를 관리해왔다. 그러나 context 는 단순하고 쉽게 전역변수를 변경하고 조회할 수 있다는 장점이 있지만 이는 다시 말해, 전역변수를 변경해야 할때마다 매번 그 상태에 직접 접근해서 변경해주어야 한다는 치명적인 단점이기도 하다.   


내가 구현하려는 상태변화가 특정한 변수가 가진 상태 자체를 false 에서 true 로 변경만 시키는 것인지, 아니면 완전히 내 통제 하에서 순서대로 일련의 함수들을 실행시켜 변화된 조건에 맞는 새로운 상태를 도출하고 싶은 것인지에 따라 context 가 더 나을 수도 있고 Redux 가 더 나을 수도 있다.  


소제목으로 달았듯이 리덕스 활용에 익숙해지는 데 첫번째 걸림돌이 리듀서와 디스패치라는 용어였다. 리덕스의 상태 관리 과정에서 마주치는 몇가지 키워드가 있다. Store, Reducer, Dispatch, Action, Subscribe 이다. 영단어의 뜻을 알고 있는 사람이라면 한글 뜻이 먼저 떠오르는 법인데, Store, Action, Subscribe 의 용도는 그 뜻과 제법 쉽게 매치된다.


Store : 변수들의 상태를 담는 유일한 저장공간. createStore API를 통해 생성한다.

Action : 실행되어야 할 행위로서, 반드시 이것을 통해서만 상태를 변경할 수 있다. 액션의 형태는 타입과 상태값을 속성으로 가지고 있는 순수 '객체' ( { } ) 로 정의된다. 예를 들어 이렇게 생겼다.

https://gist.github.com/newnorm/701912281690c2ae80893918ab28ee9b

Subscribe(listener) : 상태를 조회하는 store 의 메소드로, dispatch 호출이 일어나기 직전상태를 스냅샷으로 기록한다. 상태변화를 감지하기 때문에, 상태가 변화할 때마다 listener 로 등록해둔 함수가 호출된다.


문제가 되는 두 단어는 바로 리듀서와 디스패치이다.


Reducer

    type Reducer<S, A> = (state: S, action: A) => S


리듀서는 '함수'인데, 이전의 상태와 실행할 액션을 인자로 받아서 새로운 상태를 리턴해주는 함수이다. 리듀서라는 용어가 옳은 것인가에 관한 질문이 스택오버플로우에 올라와있었는데, (https://stackoverflow.com/questions/40599496/why-is-a-redux-reducer-called-a-reducer) 거기에 명쾌하고 흥미로운 설명이 있었다. 약간 어려워서 여러번 읽었다.


고차함수(higher order function) 라는 개념이 있는데, 이 함수는 하나 이상의 함수를 인수로 취하거나, 함수를 결과로 반환하는 함수이다. 어떤 함수가 리턴값으로 또다시 함수를 반환한다는 것은 일종의 재귀적인 동작이다.


https://gist.github.com/newnorm/ff0dbbc91e3e5a98bb72b79f8487751b


내부에서 리턴으로 실행된 함수는 함수에 이름을 붙여 선언하고 별도로 실행하는 방식이 아니라 즉시실행되는 방식으로 동작한다. 이때 내부의 함수는 외부에서 접근할 일이 없기 때문에, 내부함수의 이름이나 그 안에서 선언된 변수들을 외부에서 알 수 없다. 그래서 캡슐화, 은닉에 활용될 수 있다. 내부함수 이외에 클로저(closure)라는 이름을 갖는 이유는 이 이유 때문인 것 같다. (이 부분은 확실하지 않습니다. 깊이있는 지식을 위해서는 직접 참고자료를 찾아보시기를 추천합니다 :)  ) 


외부함수에 해당하는 outer 는 실행된 이후 생명주기가 다하지만, 내부함수 입장에서는 외부함수에서 선언된 변수 x (자유변수) 를 기억하고 있어서 생명주기가 끝난 이후에도 접근이 가능하다. 자바스크립트의 프로토타입 메소드 중 reduce()라는 메소드는 함수를 인자로 받아서 리턴시키는 고차함수의 일종이며, 리덕스의 리듀서는 이 개념을 이어받았다. 그러나 리덕스의 리듀서는 reduce메소드에서 개념만 차용한 것일 뿐, 실제 동작 방식은 다르다. 리듀서는 인자의 state 로 함수를 받거나 리턴값으로 함수를 내보내지는 않는다. 다만 기존의 상태와 액션을 인자로 받은 후, 새로운 상태를 리턴한다.


정리하자면, 리듀서는 자바스크립트 프로토타입의 메소드인 reduce() 라는 고차함수에서 개념을 차용했으며 reduce() 메소드는 내부의 재귀적인 동작에 따라 리턴값을 다시 자신의 상태로 전달해주는 동작을 통해 반환값을 누적(accumulate)하고 모든 인자를 순회한 뒤 최종적으로 하나의 결과값을 리턴한다.


리덕스의 리듀서 함수는 이 개념과 유사하지만 다르며, 자신의 이전 상태와 액션을 인자로 받아 새로운 상태를 리턴해주는 함수이다.       



Dispatch(action)

type Dispatch = (a: Action | AsyncAction) => any


디스패치는 store 의 메소드로, 액션을 실행하도록 하는 트리거 역할을 하는 함수이다. '전달하다' 라는 의미와 약간의 괴리감이 있으면서도 곰곰이 생각해보면 연관성이 느껴진다. 스토어에 접근하는 것은 무조건 액션을 통해야만 하는데, 액션은 타입과 상태를 정의해둔 순수객체일 뿐 그 자체로 함수는 아니다. Dispatch 라는 트리거를 통해 보내주어야만 실제 상태변경이 이루어지는 것이다.


정리 및 결론


리덕스를 사용하기로 마음먹은 시나리오를 가정해보면,

먼저 createStore 를 통해 Store 라는 저장공간을 생성한다. 이때 인자로는 reducer 가 필요하다. 원한다면 preloadedState (초기값) 과 미들웨어(이 글에서는 설명하지 않는다)를 두번째, 세번째 인자로 전달해줄 수 있다. reducer 는 이전 상태와 앞으로 실행되어야할 액션을 인자로 받아 새로운 상태를 리턴하는 함수이다. reducer 에 들어갈 액션은 store.dispatch() 에 객체형태의 인자로 담겨 있어야 상태값에 영향을 줄 수 있다.

https://redux.js.org/api/createstore



리덕스에서 createStore를 통해 저장공간이 만들어지고 나면, 이제 어디서든 connect 와 bindActionCreators 함수를 통해 상태변화를 일으킬 수 있다. 이것은 좀더 공부한 후에 다음 글에서 적어보겠다.




Image created by João Pedro Costa.



참고자료

https://redux.js.org/faq/store-setup#is-it-ok-to-have-more-than-one-middleware-chain-in-my-store-enhancer-what-is-the-difference-between-next-and-dispatch-in-a-middleware-function

https://velog.io/@cada/React-Redux-vs-Context-API

https://jun-choi-4928.medium.com/redux%EC%9D%98-%ED%83%84%EC%83%9D%EB%8F%99%EA%B8%B0%EC%99%80-%EC%B2%A0%ED%95%99-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-60eaa83e0b68

https://stackoverflow.com/questions/40599496/why-is-a-redux-reducer-called-a-reducer




TMI 1.

이직을 했습니다. ☺️ 리액트 네이티브로 앱개발을 합니다. 큰소리 떵떵 치고 들어왔는데 모르는 게 너무 많아서, 잔뜩 주눅이 든 채 3주를 보냈습니다.

하지만 당장 팀에 도움이 되지 못해 조급한 마음이 들더라도,
모르는 것들을 빠짐없이 잘 채워야 빈틈을 줄일 수 있을 거라고 생각하고, 기초지식을 쌓기 위해 노력하려고 합니다.
예를 들면 JAVA......

클래스 개념이 있는 언어를 조금이라도 알아야 앞으로 수월할 것 같습니다.
 


TMI 2.

https://pathas.tistory.com/226 의 말씀에 따르면,
"리덕스를 사용하면 하나의 객체를 직렬화해서 서버와 클라이언트가 프로그램의 전체 상태를 주고 받을 수 있다" 고 합니다. 직렬화가 뭔지 이해해보려고 했으나 실패하였습니다. 그래서 이번 글에 녹여내지 못했습니다..ㅠㅠ 다음기회에..



이전 08화 자바스크립트, this, 호출, 객체, 바인딩
brunch book
$magazine.title

현재 글은 이 브런치북에
소속되어 있습니다.

작품 선택

키워드 선택 0 / 3 0

댓글여부

afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari