회고: Redux 쇼핑몰 구현하기
작년 11월부터 올해까지 붙잡고 있었던 React/Redux를 적용한 쇼핑몰 프로젝트를 이제 보내주려고 한다. 사실 React만 적용한 것은 이미 작년에 다 끝났지만, 올해 Redux를 공부하면서 잠자고 있던 프로젝트를 새롭게 꺼내 올렸다.
이 프로젝트는 나의 첫 React 프로젝트이자, Redux 프로젝트다. 기존에 DOM을 이용해서 만들었던 프로젝트에서 React를 적용한 것이다. 이때 시간이 없어서 주문 부분은 완성하지 못했는데, React를 적용하면서 주문 페이지까지 완성할 수 있었다. CSS를 제대로 넣어보고 싶었는데 겨울 동안 컴퓨터공학이랑 파이썬 공부한답시고 끝내 후진 CSS가 적용된 채로 버려져 있었다. (...)
그러다가 올해 Redux 스터디를 하게 되었는데, 기존 만들어둔 프로젝트에 Redux를 적용하고 서로 코드리뷰를 해주기로 했다. 2주면 Redux를 모두 적용할 수 있을 줄 알았는데 오산이었다. Redux를 공부하면서, 기존 프로젝트의 코드도 조금씩 리팩토링 하면서 적용하는 데 시간이 좀 더 걸렸다. 다른 일들을 처리하느라 결국 목표했던 것보다는 미진한 상태로 프로젝트를 끝마쳤다.
처음 React를 적용했을 때는 사실 잘 기억이 나지 않기 때문에, 중점을 두고 코드를 짰던 부분만 정리해 보려고 한다.
컴포넌트의 역할: Presentational & Container component
Dan Abramov 선생은 재사용성과 유지보수성을 위해서 컴포넌트를 두 가지로 분류해야 한다고 이야기 한다. (Presentational and Container Components) 처음에는 PC와 CC를 구분하는 것 자체가 쉽지 않았다. '데이터를 불러오거나 변경하는 코드는 container component에 둔다' 같은 언제나 적용 가능한 원칙이 있는 경우도 있지만, 대부분은 그때그때 상황에 따라 다르다. presentational component도 상태를 가질 수 있고, container component를 presentaional component가 감싸는 경우도 있다. 댄 선생이 이야기한 것처럼 두 개 구분을 너무 명확히 할 필요는 없다. 중요하지 않을 때도 있고, 혹은 그 때가 너무 일러 결정할 수 없는 상황일 수도 있다. 마찬가지로 나도 코드를 리팩토링 해가면서 없는 상태를 넣기도 하고, 재사용 가능한 코드를 다시 분리하기도 했다. 역할과 책임을 분명히 했을 때 재사용할 수 있는 코드가 한눈에 더 확실히 들어왔다.
이는 나중에 Redux를 적용하면서도 크게 도움이 되었다. action, reducer, selector 등 각각의 역할과 책임에 맞는 코드가 작성되었을 때, 그리고 서로가 맞닿는 부분 외에는 의존하지 않도록 캡슐화를 해줬을 때 등등 더 알아보기 쉽고 고치기 쉬운 코드가 된다는 것을 깨달았다.
상태는 참으로 오묘한 것
여전히 상태라는 개념은 어렵다. 리액트 공식 문서에서는 몇 번이고 어떤 상황에서 UI가 어떻게 보일지에 집중하라고 이야기 한다.("In our experience, thinking about how the UI should look at any given moment rather than how to change it over time eliminates a whole class of bugs.") 하지만 그 사고방식을 머릿속에 넣는 것은 쉽지가 않다. 또, 화면에 표시되지 않는 상태는 어떻게 관리하는 것이 최적일까 싶다.
지금까지 내가 경험한 것은 React 내부의 상태를 사용하는 것과, Redux의 스토어를 사용하는 것 두 가지다. 리액트 내부의 상태를 사용하는 것은 직관적이다. 필요한 container component에서 상태를 저장하고, 이를 필요한 presentational component의 prop으로 보내주면 된다. 중복해서 사용하게 되는 상태 데이터는 context API를 사용하면 된다. 페이지(혹은 컴포넌트)가 리프레시 될 때에 상태 또한 비워진다. 혹시 원하는 대로 동작하지 않는다면 컴포넌트에 key를 넣어주는 것도 방법이다. 상태란 말하자면 전역변수/지역변수 같은 것이므로, 가급적이면 적절하고 좁은 범위에 상태를 위치시키는 것이 좋을 때가 많다.
Redux를 공부하기 전까지는 Redux에 대한 편견(!)이 있었기 때문에, React 컴포넌트 내부에 상태를 두는 것만이 정답이라고 생각했다. 막상 Redux를 배우고 나니 상태를 다시 생각해보게 되었다. 그러니까 상태 간의 관계라던지, 데이터로서의 상태 등을 말하는 것이다. 가령, 카트에 담긴 아이템의 정보는 사실 일전에 상품 페이지를 불러올 때 저장한 정보와 일치한다. 그렇다면 새로운 정보를 가져오기 전에 캐싱된 상태를 통해서 카트의 아이템 정보를 보여줄 수 있다. 다만, 내 쇼핑몰 프로젝트는 이렇게 Redux를 적용할 만큼 규모가 크지도 않고, 또 애초에 정보를 서버로부터 받아오는 것이 안전하기 때문에 굳이 Redux를 적용하지 않아도 상관이 없다. 또, 쇼핑몰은 애초에 페이지를 오갈 때 컴포넌트를 새로고침하고 그것이 상태에 미치는 영향도 크게 없다. 하지만 Redux를 적용해봄으로서 상태에 대해 조금 더 깊게 생각해볼 수 있었다.
리액트의 각종 라이프사이클
기껏해야 componentDidMount 정도만 썼는데, 이번에 componentDidUpdated와 static getDerivedStateFromProps를 써봤다. Redux를 사용하면 컴포넌트가 업데이트 되었다고 해서 상태가 자동으로 날라가지 않는다. 따라서 이러한 라이프사이클을 사용해서 상태를 바꿔주어야 한다. 라이프사이클을 굳이 생각하지 않아도 되는 규모의 프로젝트만을 해봤던지라 생소하기도 했다.
정규화의 중요성
Redux에 들어오는 상태를 정규화하는 것에 대해 처음 배웠고 이를 적용해보았다. 나는 백엔드나 데이터베이스를 아직 제대로 공부해본 적이 없어서 정규화라는 개념을 잘 알지 못했는데 이번에 제대로 배웠다. 데이터 정규화의 기본 개념은 각 데이터 타입은 자신의 '테이블'을 가지며, 각 '데이블'은 항목의 아이디를 키로, 항목들을 값으로 가지는 개별 항목 아이템을 저장한다. 그리고 이를 위한 참조로 항목의 아이디를 저장하며, 이 배열은 ID의 순서를 나타낸다. React나 Redux는 모두 데이터를 잘 관리하는 것을 중요하게 여기는 것 같다. 상태 업데이트할 때는 immutable해야 한다는 것이나, reducer가 순수함수여야 한다는 것 등에서 그런 생각이 들었다. 정규화된 데이터는 데이터의 중첩을 방지하고 불변데이터를 업데이트할 때 효율적으로 화면을 렌더링할 수 있게 한다. Redux 문서에도 해당 내용이 잘 정리되어 있다.
앞으로 서버에서 정규화된 데이터를 주었을 때 갸우뚱하지 않을 수 있을 것 같고, 혹여 정리되지 않은 데이터가 오더라도 normalizr 같은 라이브러리를 사용해서 관리하기 쉽게 만들 수 있을 것 같다.
함수형 프로그래밍과 자바스크립트
프로젝트를 하면서 가장 많이 느낀 것은 'React와 Redux를 잘하려면 역시 자바스크립트를 잘해야 한다'였다. DOM API를 다룰 때나 Vue.js를 슬쩍 보았을 때는 그런 생각이 들지 않았다. 그런데 React와 Redux를 공부하면서 JS를 많이 공부했고, JS 공부를 많이 할수록 React와 Redux를 더 잘 이해할 수 있게 되었다.
pagination도 다른 라이브러리를 불러오지 않고 직접 구현했던 것이나, 배열 형태의 상태를 map이나 filter 같은 메소드를 사용해서 화면을 그리는 것 등 예전에 알고리즘 문제 풀던 때보다 더 문제를 해결해야 하는 종류가 많았던 것 같다. React를 공부하면서 React가 많이 늘었다는 생각보다는 이때껏 배운 자바스크립트를 열심히 사용해봤다는 생각이 더 많이 들었다. 이건 Redux를 공부할 땐 더 그랬다. Egghead에서 Redux 강의를 들으면서 connect 함수를 제외하고는 Redux의 모든 부분을 구현해 보았다. (아니 진짜 지금 생각해도 너무 가혹하다. Redux가 뭔지 몰라서 공부하려는 사람보고 Redux의 combineReducers 함수를 만들어봅시당 데헷 하다니... 하지만 도움은 정말 많이 되었다ㅠ_^) Redux는 함수가 함수를 반환하는 여러 함수를 사용해서 바닥부터 스스로 만들 수 있었다! redux-thunk도 굳이 불러오지 않아도 JS를 잘만 사용하면 구현할 수가 있다. 나는 이때껏 함수를 반환하는 함수 같은 것은 영원히 이해할 수 없을 것만 같았는데 이제는 정말 자연스러워졌다.
하려고 했는데 못한 것
CSS 스타일링과 OrderList 부분을 redux 상태로 바꾸는 것은 이번에 못했다. 시간을 좀 더 들이면 할 수 있겠지만, 완성도를 높이자고 한다면 끝이 없을 것 같다. 일단 공부를 위한 프로젝트였기 때문에 여기서 마무리하는 것이 좋을 것 같다. 대신 시간이 될 때마다 변수/함수 이름을 좀 더 알아보기 쉬운 형태로 고치고, 스스로 코드리뷰한다 생각하고 주석을 달아보려고 한다.