이 글에서는 오랫동안 Redux를 이용한 상태관리를 해오다가 최근 SWR을 만나고, 프로젝트에서 Redux 의존성 모듈을 완전히 제거하기 까지 이른 과정과 경험을 공유하고자 합니다.
리액트를 사용한 웹개발시 상태관리의 필요성에 대해서는 더 자세히 이야기하지 않겠습니다. 당신은 이미 상태관리의 필요성과 중요성을 충분히 공감하고 있을 것입니다.
현재 어떤 상태관리 라이브러리를 사용하고 계신가요? 아마도 Redux, MobX, Recoil 3가지 중 하나를 사용하고 계실 것 같습니다. 제가 아직 Recoil은 직접 사용해 보지 않았기 때문에 Recoil 에 대한 이야기를 자세히 언급하지는 않겠습니다. 저는 주로 Redux를 사용해 왔었고 Redux의 verbose한 코딩량 때문에 MobX를 잠깐 만져본 경험은 있습니다. 그렇기 때문에 저는 Redux에 대한 내용을 중심으로 이야기를 풀어가 보도록 하겠습니다.
방법의 차이는 있겠지만 리액트 상태관리 라이브러리들이 해결하고자하는 문제는 결국 하나일 것입니다. 여러 리액트 컴포넌트에서 함께 사용할 전역 상태를 정의하고, 컴포넌트에서 각 상태에 접근하는 방법과 해당 상태를 변이시키는 방법을 제공하는 것이겠지요.
Redux는 아마도 리액트 진영에서 가장 많이 사용하는 상태관리 라이브러리일 것입니다. Redux는 선언적인 함수형프로그래밍 패러다임을 사용하여 상태를 정의하고 상태를 변이시킬 수 있는 방법을 제공합니다.
Redux를 이용한 간단한 상태관리 코드를 보겠습니다. 뻔한 코드를 보여드리는 이유는 이후 SWR을 이용한 코드와 비교하기 위함입니다. 늘 등장하는 카운터 예제입니다.
카운터의 초기상태와 변이방법을 아래와 같이 리듀서로 정의합니다.
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}컴포넌트에서는 아래와 같이 상태에 접근할 수 있습니다
import {useSelector} from 'react-redux'
function Counter(){
const data = useSelector(state => state)
return <div>count: {data}</div>
}변이 방법은 아래와 같습니다.
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1Redux는 순수함수인 리듀서와 순수객체인 액션을 통해 스토어의 상태와 변이방법을 정의합니다. 이는 매우 직관적이기 때문에 동작원리를 이해하는 것이 어렵지 않습니다. 순수함수와 순수객체 기반의 간결한 코드가 함수형 프로그래밍 특유의 아름다움을 자아냅니다. npm 모듈을 사용하지만 블랙박스가 아닌 유리상자처럼 내부가 훤히 다 보이는 것 같습니다. 저는 개인적으로 함수형 프로그래밍을 선호하기 때문에 Redux 에서 이렇게 문제를 해결하는 방법을 좋아합니다.😊
간단한 카운터 앱과 같이 동기적인 문제를 해결할 때에는 Redux는 그 아름다움을 잃지 않습니다. 그런데 요건이 변경되어 카운터의 값을 증가시킬지 감소시킬지 여부를 서버로부터 가져와야 한다고 해봅시다. 이와 같이 현실세계의 비동기적인 문제들을 만날 때 Redux 의 우아함은 흔들리게 됩니다.
Redux는 태어날 때부터 비동기적 상황을 처리할 수 있는 힘을 가지고 있지 않았기 때문이죠. Redux는 자신의 아름다움을 지켜내기 위해 비동기적 문제들에 대한 복잡하고 지저분한 처리들을 Redux 미들웨어에게 위임합니다.
현실세계의 비동기적 문제를 해결하기 위한 미들웨어들의 진흙탕 싸움이 이제부터 시작됩니다. 가장 먼저 redux-thunk가 깃발을 듭니다. redux-thunk는 비동기 액션이라는 컨셉을 이용해 나름 간단하게 비동기 문제를 처리해 냅니다.
// redux-thunk 의 비동기액션
function asyncAction(){
return (dispatch) => {
fetchHowTo('/api/how-to').then(howTo => {
dispatch({type: howTo})
})
}
}
store.dispatch(asyncAction())리듀서는 기존대로 순수함수의 모습을 지켜냈지만 액션들이 프라미스로 오염되는 것은 막을 수 없었습니다. 리듀서는 동기적으로 동작하지만 액션은 비동기로 동작하는 이 모습이 많은 사람들에게 불편함을 주었습니다. 개발자는 스토어에 액션을 던지면서도 해당 상태가 정확히 어느 시점에 변이될 지 예상할 수 없는 어려움이 생긴 것이지요.
이러한 문제를 해결하기 위해 redux-saga가 등장했습니다. redux-saga 는 액션에 포함된 비동기 로직을 별도 제너레이터 함수로 분리해 냅니다. 덕분에 비동기 액션들을 제거할 수 있게 되었고 Redux 는 다시 이전의 아름다움을 회복할 수 있었습니다.
// redux-saga 의 비동기처리 제너레이터 함수
function* sagaHowto() {
const howTo = yield call(fetchHowTo, '/api/how-to')
yield put({type: howTo})
}
function* mySaga() {
yield takeEvery("HOW_TO", sagaHowto);
}
store.dispatch({type: 'HOW_TO'})비동기 처리의 복잡함과 지저분함은 온전히 saga의 몫이 되었죠. 리덕스와 완전하게 분리된 saga들은 순수하게 비즈니스 로직으로서 관리를 할 수 있어 좋습니다. 게다가 각 saga 함수들은 제너레이터지만 순수함수이기 때문에 단위테스트를 작성하는 일도 훨씬 수월해 졌습니다.😊
하지만 이로 인해 개발자는 익숙치 않은 제너레이터의 동작원리와 redux-saga가 제공하는 여러가지 연산자들의 쓰임새들을 추가로 학습해야 하는 부담이 생겼습니다. 어짜피 개발자는 공부를 멈출 수 없는 직업인지라 운명이라 여기고 군말없이 필요한 모든 것을 학습한 후에 프로젝트의 모든 비동기 처리들을 깔끔하게 사가로 분리해 내었습니다.
그런데 비즈니스 요건이 바뀌었네요. 간단한 상태를 하나 추가할 필요가 생겼습니다. 새로운 상태를 추가하기 위해 리듀서를 수정하고 액션들을 정의하고 비동기 처리를 위해 saga 파일도 하나 추가합니다.간단한 상태를 하나 추가하려고 30줄의 코드가 추가되었습니다. 새로운 파일도 2개가 늘었습니다.😐
하지만 여전히 우리를 괴롭히는 한가지 문제가 남아 있습니다. 바로 전역 상태의 초기화와 동기화 문제인데요. 우선 초기화 문제부터 살펴 봅시다.
points 상태는 초기값으로 원격 서버의 데이터를 사용한다고 해봅시다. 그리고 points 상태를 사용하는 3개의 화면 page1, page2, page3 이 있다고 합시다. 그리고 page4 는 points 상태를 사용하지 않습니다. 그렇다면 points 상태는 어느시점에 초기화를 하는 것이 좋을까요?
App.js 전역 컴포넌트에서 points 상태를 초기화하는 것이 좋을까요? 그렇다면 page4 화면만 사용하는 사용자는 필요하지 않은 points 상태까지 fetch 하는 문제가 있겠죠.
그렇다면 해당 상태를 필요로 하는 각 화면 page1, page2, page3 에서 points 상태를 직접 초기화하는 것은 어떨까요? 아래와 같은 코드를 해당 화면들에 모두 추가하는 것입니다
useEffect(() => {
fetch('/api/points').then(data => {
store.dispatch({type: 'INIT', data})
})
}, [])page1에서 page2로 이동할 경우에는 데이터를 다시 fetch 할 필요가 없을 것이므로 아래와 같이 분기문을 추가해주는 것이 좋겠습니다.
const points = useSelector(state => state.points)
useEffect(() => {
if(points){
return
}
fetch('/api/points').then(data => {
store.dispatch({type: 'INIT', data})
})
}, [])위 코드를 page1, page2, page3에 모두 추가해 주었습니다. 반복적인 코드가 추가되는 것이 왠지 달갑지 않자만 어쩔 도리가 없습니다.😰
상태 초기화는 그런데로 적절히 마쳤습니다. 그런데 위와 같이 초기화한 상태는 화면을 새로고침하지 않는 한 최초 초기화 상태를 계속 유지하게 될 것 입니다.
만약 points 상태가 1분 단위로 변경이 발생하는 데이터이기 때문에 최소 1분 주기로 상태도 함께 갱신해야 한다는 요건이 들어오면 어떻게 해야할까요?
어렵지는 않습니다. 각 화면에서 setInterval 이나 웹소켓을 이용해 데이터를 실시간으로 동기화 해주는 로직을 추가하면 되겠지요. 작업을 마치니 코드는 점점 복잡해지고 그만큼 유지보수 해야 할 코드의 양도 많아졌습니다.

지금까지 Redux를 사용하면서 쉽게 발견할 수 있는 세 가지 문제를 살펴보았는데요, 요약하자면 다음과 같습니다.
1번은 Redux만의 문제일 것이고 MobX나 Recoil이 이에 대한 대안이 될 수도 있다고 생각합니다. 2, 3번의 문제는 MobX, Recoil 을 사용할 때에도 동일하게 만날 수 있는 문제일 것입니다. 하지만 2,3번의 문제가 엄밀하게는 상태관리 라이브러리의 문제라고 볼 수는 없습니다. 원격의 데이터를 적절하게 fetch 하는 문제는 처음부터 상태관리 라이브러리들이 해결하고자 했던 문제는 아니었기 때문입니다. 하지만 이는 상태관리 라이브러리를 사용할 때 일반적으로 부딪히는 문제이고 이후 이어질 SWR을 소개하기 위해 포함시켰습니다.
이제야 SWR을 소개할 차례가 된 것 같군요. SWR은 무엇이며 어떻게 SWR이 Redux의 앞서 언급된 문제들을 해결하고 궁극적으로 완전히 Redux를 대체할 수 있는지 2편에서 살펴보겠습니다.
Keating님의 블로그에 게재된 글을 편집하여 매드업 테크블로그에서 다시 소개드립니다.
<매드업>은 개인정보보호법에 따라 이용자의 개인정보 보호 및 권익을 보호하고 개인정보와 관련한 이용자의 고충을 원활하게 처리할 수 있도록 다음과 같은 처리방침을 두고 있습니다.
<매드업>은 회사는 개인정보처리방침을 개정하는 경우 웹사이트 공지사항(또는 개별공지)을 통하여 공지할 것입니다.
○ 본 방침은 2020년 1월 15일부터 시행됩니다.
개인정보의 처리 목적 <매드업>은 개인정보를 다음의 목적을 위해 처리합니다. 처리한 개인정보는 다음의 목적 이외의 용도로는 사용되지 않으며 이용 목적이 변경될 시에는 사전동의를 구할 예정입니다.
가. 홈페이지 회원가입 및 관리 회원 가입의사 확인, 회원제 서비스 제공에 따른 본인 식별·인증, 회원자격 유지·관리, 제한적 본인확인제 시행에 따른 본인확인, 서비스 부정이용 방지, 만14세 미만 아동 개인정보 수집 시 법정대리인 동의 여부 확인, 각종 고지·통지, 고충처리, 분쟁 조정을 위한 기록 보존 등을 목적으로 개인정보를 처리합니다.
나. 민원사무 처리 민원인의 신원 확인, 민원사항 확인, 사실조사를 위한 연락·통지, 처리결과 통보 등을 목적으로 개인정보를 처리합니다.
다. 재화 또는 서비스 제공 물품배송, 서비스 제공, 청구서 발송, 콘텐츠 제공, 맞춤 서비스 제공, 본인인증, 연령인증, 요금결제·정산 등을 목적으로 개인정보를 처리합니다.
라. 마케팅 및 광고에의 활용 신규 서비스(제품) 개발 및 맞춤 서비스 제공, 이벤트 및 광고성 정보 제공 및 참여기회 제공 , 인구통계학적 특성에 따른 서비스 제공 및 광고 게재 , 서비스의 유효성 확인, 접속빈도 파악 또는 회원의 서비스 이용에 대한 통계 등을 목적으로 개인정보를 처리합니다.
개인정보 파일 현황
가. 개인정보 파일명 : 개인정보 처리방침
개인정보 항목 : 이메일, 휴대전화번호, 비밀번호, 이름, 업체명, 제목, 광고 예산, 내용, 사업자 등록 번호, 신용카드정보, 은행계좌정보, 예금주, 생년월일, 서비스 이용 기록, 접속 로그, 쿠키, 접속 IP 정보, 결제기록
수집방법 : 홈페이지, 계약서
보유근거 : 홈페이지 회원정보 수집 등에 관한 기록
보유기간 : 5년
관련법령 : 대금결제 및 재화 등의 공급에 관한 기록 : 5년
개인정보의 처리 및 보유 기간 ① <매드업>은 법령에 따른 개인정보 보유·이용기간 또는 정보주체로부터 개인정보를 수집 시에 동의 받은 개인정보 보유,이용기간 내에서 개인정보를 처리,보유합니다. ② 각각의 개인정보 처리 및 보유 기간은 다음과 같습니다.
가. <홈페이지 회원가입 및 관리> <홈페이지 회원가입 및 관리>와 관련한 개인정보는 수집.이용에 관한 동의일로부터<5년>까지 위 이용목적을 위하여 보유.이용됩니다.
보유근거 : 홈페이지 회원정보 수집 등에 관한 기록
관련법령 : 대금결제 및 재화 등의 공급에 관한 기록 : 5년
예외사유 :
나. <재화 또는 서비스 제공> <재화 또는 서비스 제공>와 관련한 개인정보는 수집.이용에 관한 동의일로부터<5년>까지 위 이용목적을 위하여 보유.이용됩니다.
보유근거 : 계약 또는 청약철회 등에 관한 기록
관련법령 : 계약 또는 청약철회 등에 관한 기록 : 5년
예외사유 :
다. <마케팅 및 광고에의 활용> <마케팅 및 광고에의 활용>와 관련한 개인정보는 수집.이용에 관한 동의일로부터<3년>까지 위 이용목적을 위하여 보유.이용됩니다.
보유근거 : 전자상거래등에서의 소비자보호에 관한 법률
관련법령 : 소비자의 불만 또는 분쟁처리에 관한 기록 : 3년
예외사유 :
정보 주체와 법정대리인의 권리·의무 및 그 행사방법 이용자는 개인정보주체로써 다음과 같은 권리를 행사할 수 있습니다.
① 정보주체는 매드업에 대해 언제든지 개인정보 열람,정정,삭제,처리 정지 요구 등의 권리를 행사할 수 있습니다.
② 제1항에 따른 권리 행사는 매드업에 대해 개인정보 보호법 시행령 제41조제1항에 따라 서면, 전자우편, 모사전송(FAX) 등을 통하여 하실 수 있으며 매드업은 이에 대해 지체 없이 조치하겠습니다.
③ 제1항에 따른 권리 행사는 정보주체의 법정대리인이나 위임을 받은 자 등 대리인을 통하여 하실 수 있습니다. 이 경우 개인정보 보호법 시행규칙 별지 제11호 서식에 따른 위임장을 제출하셔야 합니다.
④ 개인정보 열람 및 처리 정지 요구는 개인정보보호법 제35조 제5항, 제37조 제2항에 의하여 정보주체의 권리가 제한 될 수 있습니다.
⑤ 개인정보의 정정 및 삭제 요구는 다른 법령에서 그 개인정보가 수집 대상으로 명시되어 있는 경우에는 그 삭제를 요구할 수 없습니다.
⑥ 매드업은 정보주체 권리에 따른 열람의 요구, 정정·삭제의 요구, 처리 정지의 요구 시 열람 등 요구를 한 자가 본인이거나 정당한 대리인인지를 확인합니다.
처리하는 개인정보의 항목 작성
① <매드업>은 다음의 개인정보 항목을 처리하고 있습니다.
가. <홈페이지 문의 및 관리>
필수항목 : 업체명, 이메일, 제목, 연락처, 광고 예산, 내용, 휴대전화번호, 비밀번호, 이름, 서비스 이용 기록, 접속 로그, 쿠키, 접속 IP 정보
선택항목 : 프로필 사진
나. <재화 또는 서비스 제공>
필수항목 : 업체명, 이메일, 제목, 연락처, 광고 예산, 내용, 비밀번호, 이름, 회사명, 사업자 등록 번호, 신용카드정보, 은행계좌정보, 예금주, 생년월일, 서비스 이용 기록, 접속 로그, 쿠키, 접속 IP 정보, 결제기록
선택항목 :
다. <마케팅 및 광고에의 활용>
필수항목 : 업체명, 이메일, 제목, 연락처, 광고 예산, 내용, 휴대전화번호, 이름, 서비스 이용 기록, 접속 로그, 쿠키, 접속 IP 정보
선택항목 :
개인정보의 파기<매드업>은 원칙적으로 개인정보 처리 목적이 달성된 경우에는 지체없이 해당 개인정보를 파기합니다. 파기의 절차, 기한 및 방법은 다음과 같습니다.
파기절차
이용자가 입력한 정보는 목적 달성 후 별도의 DB에 옮겨져(종이의 경우 별도의 서류) 내부 방침 및 기타 관련 법령에 따라 일정기간 저장된 후 혹은 즉시 파기됩니다. 이 때, DB로 옮겨진 개인정보는 법률에 의한 경우가 아니고서는 다른 목적으로 이용되지 않습니다.
파기기한
이용자의 개인정보는 개인정보의 보유기간이 경과된 경우에는 보유기간의 종료일로부터 5일 이내에, 개인정보의 처리 목적 달성, 해당 서비스의 폐지, 사업의 종료 등 그 개인정보가 불필요하게 되었을 때에는 개인정보의 처리가 불필요한 것으로 인정되는 날로부터 5일 이내에 그 개인정보를 파기합니다.
파기방법
전자적 파일 형태의 정보는 기록을 재생할 수 없는 기술적 방법을 사용합니다. 종이에 출력된 개인정보는 분쇄기로 분쇄하거나 소각을 통하여 파기합니다.
개인정보 자동 수집 장치의 설치•운영 및 거부에 관한 사항
① 매드업은 개별적인 맞춤서비스를 제공하기 위해 이용 정보를 저장하고 수시로 불러오는 ‘쿠키(cookie)’를 사용합니다.
② 쿠키는 웹사이트를 운영하는데 이용되는 서버(http)가 이용자의 컴퓨터 브라우저에게 보내는 소량의 정보이며 이용자들의 PC 컴퓨터 내의 하드디스크에 저장되기도 합니다.
가. 쿠키의 사용 목적 : 이용자가 방문한 각 서비스와 웹 사이트들에 대한 방문 및 이용형태, 인기 검색어, 보안접속 여부, 등을 파악하여 이용자에게 최적화된 정보 제공을 위해 사용됩니다.
나. 쿠키의 설치•운영 및 거부 : 웹브라우저 상단의 도구>인터넷 옵션>개인정보 메뉴의 옵션 설정을 통해 쿠키 저장을 거부 할 수 있습니다.
다. 쿠키 저장을 거부할 경우 맞춤형 서비스 이용에 어려움이 발생할 수 있습니다.
개인정보 보호책임자 작성
① 매드업은 개인정보 처리에 관한 업무를 총괄해서 책임지고, 개인정보 처리와 관련한 정보주체의 불만처리 및 피해구제 등을 위하여 아래와 같이 개인정보 보호책임자를 지정하고 있습니다.
▶ 개인정보 보호책임자
성명 : 강남석
직책 : CFO
직급 : CFO
연락처 : 02-1522-1060, contact@madup.com
※ 개인정보 보호 담당부서로 연결됩니다.
▶ 개인정보 보호 담당부서
부서명 : General Affairs Team
담당자 : 민상준
연락처 : 02-1522-1060, contact@madup.com
② 정보 주체께서는 매드업의 서비스(또는 사업)을 이용하시면서 발생한 모든 개인정보 보호 관련 문의, 불만처리, 피해구제 등에 관한 사항을 개인정보 보호책임자 및 담당부서로 문의하실 수 있습니다. 매드업은 정보주체의 문의에 대해 지체 없이 답변 및 처리해드릴 것입니다.
개인정보 처리방침 변경
① 이 개인정보처리방침은 시행일로부터 적용되며, 법령 및 방침에 따른 변경 내용의 추가, 삭제 및 정정이 있는 경우에는 변경사항의 시행 7일 전부터 공지사항을 통하여 고지할 것입니다.
개인정보의 안전성 확보 조치 <매드업>은 개인정보보호법 제29조에 따라 다음과 같이 안전성 확보에 필요한 기술적/관리적 및 물리적 조치를 하고 있습니다.
가. 정기적인 자체 감사 실시 개인정보 취급 관련 안정성 확보를 위해 정기적(분기 1회)으로 자체 감사를 실시하고 있습니다.
나. 개인정보 취급 직원의 최소화 및 교육 개인정보를 취급하는 직원을 지정하고 담당자에 한정시켜 최소화 하여 개인정보를 관리하는 대책을 시행하고 있습니다.
다. 내부관리계획의 수립 및 시행 개인정보의 안전한 처리를 위하여 내부관리계획을 수립하고 시행하고 있습니다.
라. 해킹 등에 대비한 기술적 대책 <매드업>은 해킹이나 컴퓨터 바이러스 등에 의한 개인정보 유출 및 훼손을 막기 위하여 보안프로그램을 설치하고 주기적인 갱신·점검을 하며 외부로부터 접근이 통제된 구역에 시스템을 설치하고 기술적/물리적으로 감시 및 차단하고 있습니다.
마. 개인정보의 암호화 이용자의 개인정보는 비밀번호는 암호화 되어 저장 및 관리되고 있어, 본인만이 알 수 있으며 중요한 데이터는 파일 및 전송 데이터를 암호화 하거나 파일 잠금 기능을 사용하는 등의 별도 보안기능을 사용하고 있습니다.
바. 접속 기록의 보관 및 위변조 방지 개인정보처리시스템에 접속한 기록을 최소 6개월 이상 보관, 관리하고 있으며, 접속 기록이 위변조 및 도난, 분실되지 않도록 보안기능 사용하고 있습니다.
사. 개인정보에 대한 접근 제한 개인정보를 처리하는 데이터베이스시스템에 대한 접근 권한의 부여,변경,말소를 통하여 개인정보에 대한 접근통제를 위하여 필요한 조치를 하고 있으며 침입차단시스템을 이용하여 외부로부터의 무단 접근을 통제하고 있습니다.
아. 문서 보안을 위한 잠금 장치 사용 개인정보가 포함된 서류, 보조저장매체 등을 잠금 장치가 있는 안전한 장소에 보관하고 있습니다.
자. 비인가자에 대한 출입 통제 개인정보를 보관하고 있는 물리적 보관 장소를 별도로 두고 이에 대해 출입통제 절차를 수립, 운영하고 있습니다.