이 팀원들과 함께라면 지옥을 불사르고라도 프로젝트를 성공적으로 이끌어내고 싶었다.
생각해보니, 교육 기관에서 프로젝트2를 함께 해나갈 팀원 명단을 내려주기까지 텀이 꽤 걸렸다. 그래서 저번 프로젝트에서 얻은 교훈으로 그래, 이번엔 천천히 하지
라는 생각을 하며 기다리고 있었다. 지인과 저녁을 먹고 커피 한 잔 해서 들어오고 있었다. 이젠 내가 교육을 받고 있었나 기억이 희미해지고 있었는데 딱 명단이 나왔다. 보자마자 환호를 질렀다. 여기 사는 고시원 식구들이 점잖아서 그렇지, 아마 다른 곳이었으면 한 소리 먹었을지도 모른다. 이유는 그간 내가 꼭 팀이 되어 같이 작업을 했으면 하는 사람들 모두, 함께 팀이 되었기 때문이다.
못 할게 없었다. 이 팀원들과 함께라면 지옥을 불사르고라도 프로젝트를 성공적으로 이끌어내고 싶었다. 사실 지금 와서 돌이켜보면 나의 이 열정 때문에 불편한 사람도 있었을 것 같다. 사실 교육기간 잠깐 진행하는 2 주간의 프로젝트에서 얼마나 열심히 하고 싶겠나. 정도껏 포트폴리오로 사용할 수 있을 만큼의 퀄리티만 맞추고 각자의 목표로 돌아가는 게 더 낫다고 생각하는 사람들도 많을 것이다. 하지만 적어도 지금껏 같이 달려왔던 팀원들 모두 한 마음으로 이번 프로젝트 성공을 바라며 작업했다고 감히 말 할 수 있다.
디자인이 정해지지 않았다? 이제 디자인은 뱃사공 인원만큼 높은 산으로 올라간다.
2주 간 모든 시간 즐겁고 행복하게 보냈다고는 할 수 없다. 진행 중 생기는 문제들은 항상 골머리를 앓게 했고, 같은 파트에서 작업하는 팀원과는 협업에서 늘 일어나는 충돌이 있기 마련이었다.
나는 이번 프로젝트에서 프론트엔드를 맡았다. 다른 두 분이 나보다 더 백엔드를 원하고 있었고, 나보다 더 백엔드에서 역량을 보여줄 수 있는 사람들이었기 때문이다. 사용자들에게 보여지는 화면을 구성하고 기능을 제공하는 역할을 맡은 만큼 나는 누가봐도 인정해줄 수 밖에 없는 디자인을 기획하고 싶었다.
하지만 아름다움
이란 것은 정말 주관적인 부분이다. 내가 미적으로 괜찮다고 생각하는 디자인이 다른 사람에게도 똑같이 보이지 않을 수 있다. 그리고 상대방도 똑같이 내가 그리는 화면 요소들이 다 멋있다고 생각하지 않을 수 있다. 내가 사용자가 이용하기 편하다고 생객하며 작성한 애니메이션은 누가보면 거추장스럽고 올드하게 보일 수 있다.
보통 프로젝트의 디자인은 디자인을 잘 하는 사람이 미리 밑그림을 그려주면 그대로 화면에 그려주기만 하면 되어서 문제가 없다. 하지만 디자인이 정해지지 않았다? 이제 디자인은 뱃사공 인원만큼 높은 산으로 올라간다. 이번 프론트엔드 팀이 그랬다. 각자 원하는 디자인이 있었고, 결국 충돌이 일어나는 지점이 생겼다.
하지만 프로젝트의 성공의 관점에서 봤을 때, 나의 디자인을 고집하는 것보다 프로젝트에서 기획한 제품의 디자인 일관성과, 기획과의 일치를 생각해야 했다. 다행히 팀원 분이 디자인에 대해 열성적으로 임해주고 있었기에 차라리 해당 부분에 대한 권리를 완전 위임하고 피드백을 주는 게 낫다 판단되었다.
프로젝트 내내 쉬운 것이 없었다. 오랜만에 만져보는 프론트엔드, 그리고 디자인 협업. 심지어 새로운 도전들까지 있었다. 그 중 이번에는 새로운 도전들에 대해서 다뤄보겠다.
매번 지도 API를 쓰는 프로젝트를 바라보며 언젠간 나도 지도 API를 써서 멋진 프로젝트를 만들고 싶다는 생각을 했었다. 이제는 옛날이 된 얘기지만, COVID-19
초기 때, 어린 학생 여럿이서 만든 국내 코로나 발생지를 확인할 수 있게 했던 서비스가 있었는데, 지도 하나 그리지 못해서 부러워하고만 있었다.
자, API를 사용하기로 했으면 꼭 관련 API를 사전에 공부를 하도록 하자. 이번 프로젝트 중에는 K사 API를 사용을 하다가 G사로 옮겨왔다. 해당 작업만 2주의 3일을 뺏어갔다. 20%
. 이유는 K사에서 우리 프로젝트에서 주요한 기능 하나를 제공하지 않고 있다는 것을 해당 개발을 마치는 거의 막바지에 알아버렸기 때문이다.
리액트를 사용할 때마다 느끼는 것은 props 관리의 복잡성이 금방 늘어난다는 것이다. 사실 우리 프로젝트에서는 깊게 들어가봤자 props 단계가 2단을 거치기 때문에 조금의 불편함을 감수할 만 했다. 하지만 우리는 그 조금의 불편함도 불편하게 느끼는 개발자 아니던가. 이번 기회에 리액트 어플리케이션 상태변수가 아닌, 전역적으로 상태값을 관리할 수 있는 상태 관리 라이브러리 React-Redux
를 도입하기로 했다. Recoli
과 여타 다른 라이브러리들이 많던데 이번에 React-Redux
를 선택한 이유는, 리액트 컴포넌트에서 상태값을 가져와 사용하는 모습은 당장 사용하고 있는 Hooks와 크게 다를 바가 없었기 때문이다.
드디어 프로젝트2 도 마무리하게 되었다. 두 프로젝트 모두 다 최선을 다했다는 것에 만족한다. 그리고 이번 두번 째 프로젝트는 심지어 화력까지 갖추어 하고 싶은 것 다 할 수 있었던 것에 기쁘다. 이번 프로젝트 동안 겪었던 모든 경험들은 다음 프로젝트에서도 모습을 드러낼 것이다.
이번 프로젝트 주제로는 토큰 기반 인센티브 커뮤니티
를 만드는 일이었다. 어떤 주제여도 상관없이 인센티브 기반으로 응집이 되는 커뮤니티 사이트를 만들면 되었다. 사실 관련하여 이미 생각해놓았던 주제가 있었다. 바로 맛집 리뷰였다. 맛집에 대한 정보를 공유하는 것을 좋아하는 사람들이 커뮤니티를 이뤄놓은다면 현재 존재하는 리뷰 사이트들보다 더 진실한 정보들이 담길 것이라 생각했기 때문이다. 뒷광고 없는, 블랙 컨슈머 없는, 서로가 진실한 맛집 리뷰를 올리는 커뮤니티가 되게 만든다는 것이 주 목표였다.
이번 프로젝트에서는 프론트엔드를 맡기로 하였다.
React-Redux
이야기를 하려고 한다. 항상 리액트 이용한 프로젝트를 진행하다보면 반복되는 문제가 있었다. 바로 props
관리 문제다. 리액트는 컴포넌트간의 데이터 이동을 props
를 통한 상위 컴포넌트에서 하위 컴포넌트로의 단방향 데이터 흐름 방식이라는 원리를 이용한다. 사실 이는 데이터의 관리를 용이하게 만들기 위한 방법이었다.
하지만 대부분의 서비스는 사용자와 직접적으로 상호작용을 하는 요소들이 컴포넌트의 끝자락에 위치하고 있다는 것이 문제다. 만약 상위 컴포넌트에서 관리하는 상태값을 임의의 요소를 통해 변경한다고 하면, 해당 요소까지 상태값을 변경하는 함수를 전달해줘야 한다. 이는 즉 중간에 거치는 컴포넌트들이 많을 수록 한 번 수정할 때마다 변경해야하는 값들이 많다는 것이다. 덕분에 기획 끝나자마자 어떤 함수 이름을 쓸지 부터 정하고 있는 모습을 확인할 수 있다.
이는 모두 리액트 어플리케이션에 종속되는 상태값으로 인해서 생기는 문제다. 리액트 어플리케이션을 벗어나 전역적으로 상태값을 저장한다면 이제 props를 사용할 필요가 없을 것이다. 이를 가능하게 하는 것이 redux
나 recoil
같은 상태 관리 라이브러리다. 이번에는 redux
를 부분적으로 적용하여 해당 문제를 풀기로 하였다.
Redux도 자체적으로 단방향 데이터흐름을 보인다. Action → Dispatch → Reducer → Store
의 순서다. Action
은 미리 상태값을 어떻게 변경하기로 했는지에 대한 약속이다. 그리고 Dispatch 는 해당 Action을 실행시킬 수 있게 Reducer에 전달한다. 이후 Reducer는 Action에 들어와 갱신되는 값들을 확인한 뒤 Store에 저장한다. Store는 갱신된 값들로 여러 곳에서 쓰이고 있는 해당 상태값의 view를 렌더링 해준다.
이번 프로젝트 같은 경우는 인증 관련한 상태값들을 Redux로 관리해주었다. 서버에서 발급해준 AccessToken
과 유저를 식별할 수 있는 userId
그리고 로그인 여부를 판별할 수 있는 isLogin
이다. 이 값들은 대부분의 페이지에서 이용하게 된다.
위의 제시된 그림처럼, 전역으로 관리를 하여 필요한 컴포넌트에서 불러와 값을 사용할 수 있게 만들었다. 따로 props
를 통하여 하위 컴포넌트에 전달할 필요가 없게되었다. 덕분에 우리는 인증 관련해서는 복잡한 props 관리를 할 필요가 없었다.
[Header.js 상태 변수]
const Header = ({user, liftUser}) => {
// Navigator
const navigator = useNavigate();
// Header State Var
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const [sidebarModalIsOpen, setSidebarModalIsOpen] = useState(false);
const [loginModalIsOpen, setLoginModalIsOpen] = useState(false);
// Login Global State Redux
const isLogin = useSelector((state)=>state.auth.isLogin);
const accessToken = useSelector((state)=>state.auth.accessToken);
const dispatch = useDispatch();
// ...생략
[Mypage.js 상태 변수]
const WritePage = ({user, liftUser}) => {
const navigator = useNavigate();
// Login Global State Redux
const isLogin = useSelector((state)=>state.auth.isLogin);
const accessToken = useSelector((state)=>state.auth.accessToken);
// New Review State Variable
const [title, setTitle] = useState("");
const [locationName, setLocationName] = useState("");
const [locationId, setLocationId] = useState(null);
const [content, setContent] = useState("");
const [images, setImages] = useState(null);
// ...생략
위에 user
props와 isLogin, accessToken
의 대비된 모습을 확인하면 된다. 사실 isLogin과 accessToken도 상위 컴포넌트에서 props로 전해줘야하는 데이터다. 하지만 redux를 통해 굳이 다른 컴포넌트와의 데이터 연결 없이 해당 컴포넌트에서 바로 해당 데이터를 가져온다는 선언 형식으로 가져와 사용할 수 있다. 제일 직관적으로 이해할 수 있는 코드를 들어 설명해보았다.
이번 프로젝트의 개선할 점은 정말 무긍무진하게 많다. 이게 문제가 많다는 것이 아니라, 잠재력이 많다는 것이다.
[로그인 시연]
[리뷰 작성]
[NFT 민팅]
[토큰 전송]