본문 바로가기
Project/Client

[React Native] Redux로 상태 관리하기

by 세이(Sayer) 2023. 5. 15.
728x90
[목차]

0. 들어가며 - '우리두리'를 개발하고 있어요!
1. Redux란?
2. Redux 직접 적용해보기

 

0. 들어가며

 

"우리 둘이 만들어가는 이야기, 우리두리"

GPT 기반 릴레이 동화 창작 서비스 '우리두리'

졸업프로젝트로 '우리두리'라는 서비스를 만들고 있습니다. 우리두리는 GPT를 기반으로 하는 '아동용 릴레이 동화 창작 서비스'예요.

작년 하반기에 기획을 시작했는데, 방학을 지나면서 ChatGPT의 인기가 갑자기 급상승하게 되어 GPT를 사용하는 우리 서비스도 많은 학우들의 관심을 받게 되었어요. GPT가 대중에게 가장 친숙한 기술이 된 지금, 기왕이면 프로젝트를 아주 멋지게 끝내보자는 마음가짐으로 막판 스퍼트를 올리고 있습니다.

 

 

우리두리는 간단하게 위와 같은 3단계로 이루어진 서비스입니다.

물론 사용자 조사를 바탕으로 아동의 책읽기, 개인별 수준에 맞는 적절한 국어 학습 방법 등에 대한 니즈를 도출하여 서비스를 기획했지만, 기획 외적으로 서비스만을 고려했을 때 저희는 다음과 같은 목표를 세웠었어요.

1. 적은 리소스로도 지속 가능하도록 '유저가 직접 데이터를 채우는' 형태의 서비스를 만들자.
2. 반복되는 서비스 플로우를 사용자가 지루하게 느끼지 않도록 '오락적인 요소'를 마련하자. 모든 서비스는 (특히나 아동용 서비스는 더) 재밌어야 사용한다!


그래서 아동이 동화를 창작하는 과정을 3단계로 나눴어요. 동화 창작 전/중/후 과정을 나눠서 한 구간이 오래 반복되어 지루함을 느끼는 부분을 줄이려고 노력했고, 군데 군데 오락적 요소를 넣어 사용자가 재미있게 서비스 전 과정에 참여할 수 있도록 했습니다.

그 결과 아래와 같은 서비스가 탄생했습니다! 간단히 이미지로 메인 기능 설명을 첨부해봅니다.

 

 

최대한 볼륨을 줄여서 기능 하나만 남기려고 열심히 기획을 했는데도 막상 개발에 들어가니까 신경써야 할 부분이 많더라고요. 😂

특히 우리두리의 경우 동화를 만드는 동안 설정값만큼 뷰가 반복되고, 창작된 문장은 어떤 식으로 서버에 누적해 저장될 것이며, GPT에는 어디서부터 어디까지 문장을 넘길 것인지, 사용자가 느끼는 딜레이를 최소화하기 위해서 어떤 방식으로 서비스 플로우를 기획할 것인지...에 대한 다양한 고민을 해야했어요.
그 중에서도 사전 설정 단계를 완료하고 난 이후부터 티켓 제작이 끝날 때까지 동화에 대한 정보를 클라이언트에서 모두 갖고 있으면서 서버와 계속해서 통신을 하며 정보를 갱신해야 한다는 부분이 프론트 개발을 하는데 있어서 가장 큰 도전 요소였던 것 같습니다.

이 부분을 개발하면서 저는 상태 관리를 위해 redux와 서버 연결을 위해 axios를 사용했는데요! 오늘 글에서는 이 내용에 대해 다뤄보겠습니다.

 


 

1. Redux란?

 

React Native에서 가장 중요한 건 결국 '상태 관리'입니다.

우리두리의 경우 릴레이 창작을 통해 책을 만드는 서비스이기 때문에, 사용자가 문장을 만들 때마다 정보를 갱신해야 했어요. (물론 당연히 로그인 이후에 계정 정보가 바뀌기 전까지 사용자 정보를 계속해서 갖고 있으면서, 앱에 접속할 때마다 그 정보를 계속해서 갱신해야 하기도 했죠.)

단계(뷰) 사전 설정 동화 창작 티켓 제작
이 정보들을 서버로 보내요! 인물(1~3명 랜덤 설정)
배경 장소
책의 길이
창작한 문장들
문장에 따라 생성된 그림
커버 이미지
티켓 제목
보낸 정보를 서버에 저장하려면 이 정보들이 함께 필요해요! 사용자 정보
(누가 만든 책인지 알아야)
책의 고유 번호

책의 길이
(몇번 반복할지 알아야)

인물, 배경 장소 정보
(그 정보를 바탕으로 문장을 창작해야 함)
책의 고유 번호

각 뷰에서 사용하는 데이터를 간소화해서 적어둔 표입니다. 보시면 여러 뷰에서 같은 정보들이 필요하다는 게 보이시죠?

가장 상위 파일인 App.js에 모든 state를 넣어뒀더니 모든 컴포넌트들에서 가져다가 쓸 수 있었지만, 열심히 조각조각 파일을 분리해놓은 덕분에 state를 props로 내려주고 내려주고 또 내려줘야 했어요. 그러다보니 어디에선가 하나씩 빼먹고 왜 데이터가 안 들어오냐며 삽질을 하는 시간이 길어졌습니다. 그리고 해당 props를 사용하지 않는 컴포넌트를 거쳐갈 경우 불필요한 리랜더링이 발생하는 문제도 있었고요.



그래서 Redux를 도입하기로 했습니다!


Redux는 간단히 말해서 그런 상태(state)들을 따로 빼놓는 저장소(store)를 제공하는 React의 라이브러리예요. React Native에서도 다행히 사용할 수 있답니다.

이런 방식으로 불필요한 리랜더링 없이 바로 필요한 정보를 외부에서 가져다가 쓸 수 있다는 게 가장 큰 장점이죠!

1) store에 state를 업데이트해줘!라는 이벤트가 발생했을 때
2) dispatch(인자 : 액션을 생성하는 함수)를 호출하면
3) 생성된 액션의 type에 따라 reducer에서 state의 변화를 일으키는 방식
...이라고 하는데 저는 글만 읽으면 잘 이해가 안 되더라고요. 직접 Redux를 만들어 보면서 살펴봅시다. 😁

 


 

2. Redux 적용해보기

 

(1) Redux 설치

사용하시는 패키지에 따라 npm 혹은 yarn으로 다운로드를 받아줍니다.

npm install redux react-redux --save
yarn add redux react-redux --save

 

 

(2) /modules/파일.js에 state와 action 정의

열심히 useState Hook을 사용해서 정리해놨던 state들을 정의해줍니다. 저는 modules라는 폴더를 만들어서 내부에 redux와 관련된 파일들을 모두 관리하고 있어요. presetStory.js라는 이름의 파일을 만들었고, 액션 정의 부분 / state 초기 형태 정의 /  reducer의 내용을 적어뒀어요. 코드와 함께 봅시다!

 

 

* initState

설명을 위해 양쪽에 두고 캡쳐했을 뿐, 쭉 이어지는 하나의 파일입니다!

먼저 사용할 state를 정의하고 초기화를 시켜야겠죠? 바로 하얀 박스 부분입니다. 저는 사전 설정과 관련된 정보를 관리하려고 하기 때문에 사전 설정에서 정해지는 인물, 배경(장소), 책의 길이에 대한 상태를 초기화해 두었습니다.

그렇다면, 사용자가 사전 설정 뷰에서 캐릭터 카드를 뽑으면 비어있는 배열에 캐릭터 이름이 업데이트가 되어야겠죠? state를 업데이트 해야 되는 이벤트가 발생했을 때 생성되는 것이 바로 액션입니다!

 

 

* action & reducer

presetStory.js

하얀 박스가 바로 사전 설정에서 랜덤 뽑기 결과로 나온 인물(1~3명) 정보와 관련된 액션입니다. 액션은 말 그대로 동작이라고 이해하시면 돼요. 특정 조건(이벤트)이 발생하면 미리 정의한대로 동작해라!

왼쪽의 흰 박스부터 보겠습니다. getPreCharInfo라는 이름의 함수(액션 생성기)를 만들었어요. 아래에서 보겠지만 사전 설정 페이지에서 캐릭터를 뽑고 나서 뽑힌 캐릭터 리스트를 매개변수(첫 번째 빨간 박스 character)로 받아오게 될 거예요. 저 함수가 컴포넌트에서 호출이 된다면 type과 매개변수로 받아온 character가 return이 되게 됩니다.

그러면 오른쪽 아래의 Reducer로 넘어가볼까요? 
위에서 정의해둔 initState와 action을 매개변수로 가져옵니다. switch문으로 구현을 해두어서 action의 type에 따라 분기처리가 되는 방식이에요. getPreCharInfo 함수는 위에서 정의해둔 바에 따라 'presetStory/GET_PRESET_CHARACTER_INFO'라는 이름의 액션을 호출하기 때문에 오른쪽 흰 박스를 실행하게 됩니다.  initState로 초기화해둔 state의 character를 action의 character 값으로 갱신을 시키는 거죠!

즉, reducer는 기존에 redux가 갖고 있는 state값이 action에 의해 변화될 때, action에 의해 변화된 state 값의 변화를 반영해서 새로운 state를 반환하는 역할을 한다고 정리할 수 있겠네요! 이제 이미지의 빨간 박스들이 같은 값을 의미한다는 걸 눈치 채셨나요? 결국 이 파일은 컴포넌트에서 받아온 값으로 state를 업데이트 시키는 부분이었습니다.

 

 

(3) store.js 생성

그러면 이 reducer들을 모아 외부에 state들을 저장하는 창고인 store를 만들어야겠죠? store는 다음과 같습니다.

우리두리는 기능별로 reducer 파일을 분리해둬서 makeStory 말고도 presetStory, ticket이라는 파일이 있습니다. 만들어둔 모든 reducer들을 import해온 후 combineReducers를 사용해 묶어서 rootReducer로 결합시켜줬어요. 하얀 박스 부분입니다.

combineReducers는 이름 그대로 여러 reducers들을 하나의 객체로 모아서 반환해주는 함수입니다. reducers의 개수가 많을수록 유용하겠죠? 받아오는 상태값을 콘솔에 찍어보면 아래와 같이 나옵니다.

리듀서 이름 : {"상태 이름1" : 상태값1, "상태 이름2" : 상태값2 ... } 이런 식으로 출력된다.

 

그 이후 configureStore를 사용해서 모든 reducer(rootReducer에 다 결합시켰으니까)의 모든 기본값들을 store에서 사용할 수 있도록 해줬습니다. 그 store를 export 해주세요!

cf) 구글링을 해봤을 때 createStore를 사용하는 예제가 많았는데요! createStore를 사용하니까 빨간 줄이 떠서 찾아보니 configureStore 사용을 권장한다고 하더라고요. configureStore는 redux library의 표준 함수인 createStore를 추상화해서 redux의 기본 설정 과정을 자동화하는 도구인데요. 이 부분에 대해 궁금하시면 아래 글을 읽어보시는 것을 추천합니다 :)

 

Redux Toolkit (리덕스 툴킷)은 정말 천덕꾸러기일까?

Redux Toolkit 최근 훅 기반의 API 지원이 가속화되고 React Query, SWR 등 강력한 데이터 패칭과 캐싱 라이브러리를 사용하면서 리덕스 사용이 줄어드는 방향으로 프론트엔드 기술 트렌드가 변화하고 있

blog-wp.hwahae.co.kr

 

 

(4) Provider 설정 - 가장 상위의 index.js

열심히 만든 store, 모든 컴포넌트에서 사용할 수 있어야겠죠? 가장 상위 폴더의 index.js에 아래와 같이 Provider로 App 컴포넌트를 감싸준 다음 store를 props로 내려줍니다.

 

 

(5-1) 컴포넌트에서 state 호출하기 - useSelector

열심히 만든 store의 state들을 호출하고 새로운 값을 넣어줄 수 있는지 확인해봅시다!

먼저 호출하는 과정입니다. useSelector를 사용해서 state 정보를 가져올 수 있는데요. 아래와 같이 useSelector를 사용해서 변수에 그 값을 불러오고 console.log로 한번 찍어보세요. 값이 제대로 들어오는지 먼저 확인해봅시다!

게시글 작성을 위해 character만 불러왔습니다.

 

 

(5-2) 컴포넌트에서 action 생성하기 - dispatch (useDispatch)

state를 호출하는데 성공했으니, 이제 새로운 값을 store로 보내는 작업을 해봅시다. 위에서 먼저 살펴봤듯 store가 갖고 있는 state 값을 변화시키는 action을 호출해야 되겠죠? 액션을 store에 운반해주는 역할을 하는 것이 바로 dispatch()입니다.

useDispatch를 import해주고, dispatch를 선언한 이후 아래와 같이 액션 생성기를 인자로 넣어주면 됩니다.

예시로 사용하기 위해 간소화한 코드입니다. 코드 전문은 깃허브에서 확인 가능합니다.

이 파일은 하단바에 있는 MainButton 컴포넌트 파일이에요. 버튼 컴포넌트는 character를 뽑는 사전 설정의 컴포넌트와 상하관계에 없는 별도의 컴포넌트인데요. dispatch를 사용해 버튼이 press될 때 getPreCharInfo 함수를 호출하여 state의 character 값이 갱신되도록 했습니다! 기존에 props로 넘겨줘야 했던 방식보다 훨씬 깔끔합니다. :)

 


 

이렇게 Redux를 적용해 상태를 효율적으로 관리하는 방법에 대해 살펴봤는데요!

우리두리의 경우 서버 연결을 하기 때문에 axios를 사용해서 async-await을 사용해 서버로부터 정보를 받아와야 했어요. 동기적으로 받아온 정보들을 마찬가지로 redux에 갱신하는 과정이 필요했는데, 그때 사용할 수 있는 여러 라이브러리가 있더라고요. 지금은 Redux Saga를 적용중에 있어서, 나중에 기회가 된다면 그 부분에 대해서도 글을 작성해보겠습니다.

직접 사용자를 만나서 인터뷰를 하고 니즈를 도출해서 서비스를 기획하고, 매주 UX를 고려하며 서비스 플로우를 뜯어고치고, 전체 뷰, 발표 자료, 포스터까지 디자인하고, 서버 개발을 끝낸 후 프론트 개발까지 했던 바쁜 1년이었습니다. 서비스를 개발할 때마다 느끼는 거지만, 역시 가장 핵심적인 기능만을 남겨 빠르게 상품을 완성하고 검증하는 과정이 중요하다는 것을 깨닫는 시간이었어요. 퀄리티에 상관 없이 '일단 완성하는 것'이 가장 어려운 관문이라는 걸 또 한 번 느낍니다.

아마도 대학 생활 마지막 개발이 될 졸업프로젝트가 끝나간다니 감회가 새롭네요! 마지막까지 열심히 달려보겠습니다. 👍

 

우리두리

GPT 기반 아동용 릴레이 동화 창작 서비스. 우리두리 has 5 repositories available. Follow their code on GitHub.

github.com

 

댓글