dwitter 1.2.0 업데이트

Hyuno Choi·2021년 8월 8일
0
post-thumbnail

2021년 8월 8일

안녕하세요, dwitter 1.0.1 패치 포스팅을 한 지 얼마 지나지 않은 것 같은데 또 업데이트 소식으로 포스팅을 하려고 합니다.🙃 사실 dwitter는 지금까지 진행한 토이 프로젝트 중에 가장 애정이 가는 친구입니다. 아직은 엉성하지만 친구들과 실시간으로 대화가 가능한 플렛폼을 제 손으로 만들었다는 것만으로도 나름 즐겁습니다.

설계

목표상태

이번 업데이트는 <dwitter 클론코딩 복습노트> 마지막 포스팅에서 계획했던 좋아요👍 버튼 추가입니다. 제가 생각하는 "좋아요" 기능은 다음과 같은 조건을 충족시켜야 했습니다.

  1. 좋아요 버튼과 좋아요 숫자 화면에 표시
  2. 좋아요 버튼은 토글이어야 함(ON or OFF)
  3. 좋아요 수의 변화는 실시간으로 화면에 반영되어야 함
  4. 기존 DB 구조를 최대한 활용하여 만들어야 함

이 조건들을 충족할 수 있는 좋아요 기능을 만들기 위해 기존의 메시지 데이터가 불러와지는 과정을 떠올리며 나름대로 구상을 해보았습니다.

굉장히 엉망진창입니다만, 초기에 구상한 좋아요 기능의 설계는 다음과 같습니다.

질문1: '좋아요' 데이터를 어디에 저장할까?

기능 구현을 앞두고 가장 먼저 든 생각입니다. 현재 dwitter는 구글의 Firebase를 데이터베이스로 사용하고 있습니다. 그리고 유저 인증과 드윗(메시지) 데이터를 데이터베이스에 저장합니다. 선택지는 두 개입니다. '좋아요' 데이터는 유저 객체 안에 추가할 수도 있고 드윗 객체 안에 추가할 수도 있습니다.

방법1: 유저 객체에 추가한다.

처음 시도한 방법은 좋아요 데이터를 유저 객체에 저장하는 것이었습니다. 보다 자세히 말하면,

  1. 화면에 표시되는 모든 메시지는 고유 id 값을 가집니다.
  2. 사용자가 메시지의 좋아요 버튼을 누르면 해당 메시지의 id를 유저 데이터 내부의 '좋아요 표시한 리스트' 안에 저장합니다.
  3. 드윗을 불러올 때는 불러오는 모든 드윗의 id 값과 현재 유저의 데이터 안에 있는 '좋아요 표시한 리스트'에 저장된 id 값을 대조합니다.
  4. 만약 드윗의 id 가 리스트 안에 있다면 좋아요를 이미 누른 것으로 처리하고, 리스트 안에 없다면 좋아요를 누르지 않은 것으로 처리하는 방법입니다.

방법1을 고안한 이유?

효율성 측면에서

좋아요 데이터를 유저 객체에서 관리하면 드윗의 수가 많아졌을 때 처리가 빠르다는 장점이 있을 것 같았습니다. 좋아요 데이터를 드윗 객체에서 관리하는 경우를 가정해봅시다.

  1. 한 드윗에 10억 명의 사람이 좋아요를 눌렀습니다.
  2. 그 드윗에 지금 접속한 유저가 좋아요를 눌렀는지 알기 위해서는 10억 개의 id 중에서 현재 유저의 id를 찾아야 합니다.

그러나 좋아요 데이터를 유저 객체에서 관리하면 화면에 로딩되는 드윗의 id 를 현재 유저의 '좋아요 리스트' 안의 id 들과 대조하기만 하면 됩니다.

향후 기능 구현에서

또한 좋아요 데이터를 유저 객체에서 관리하면 '좋아요 표시한 드윗 모아보기' 같은 기능을 추후 구현하는 데 훨씬 편할 것 같았습니다.

방법1의 문제들

Firebase 유저 객체 커스텀

우선 가장 큰 문제는 Firebase의 인증 서비스에서 사용하는 유저 객체의 속성과 메소드가 고정되어 있다는 것입니다. 좋아요를 표시한 드윗의 id 를 저장하기 위한 새로운 속성을 직관적인 방식으로 유저 객체에 추가할 수 있는 방법은 없는 것 같습니다.

데이터 처리의 복잡성

결국 방법1의 범위 안에서 대안으로 선택한 것은 '좋아요 리스트' 콜랙션을 Firebase에 별도로 만들고, 그 안에 유저별로 도큐멘트를 각각 만들어 유저 객체 외부에서 좋아요 리스트를 관리하는 것이었습니다.

직접 코드를 짜보니 문제가 바로 드러납니다. 우선 이런 방법을 사용할 경우 콜렉션의 개수가 기능을 추가할 때마다 늘어나게 됩니다. 그리고 기능 구현이 복잡해집니다. 좋아요 개수는 드윗 콜렉션에, 유저별 '좋아요 리스트'는 별도의 콜렉션에 저장하고, 나중에 유저 데이터를 불러온 다음 '좋아요 리스트' 데이터를 다시 불러와야 하는 등 여간 번거로운 게 아닙니다.

기존 구조 활용 불가

가장 심각한 문제는 기존의 데이터 전달 흐름을 사용하지 못한다는 것입니다. 기존 드윗 데이터의 경우,

  1. Home 화면에서 DB로부터 불려온 다음
  2. 프롭으로 각각의 드윗 컴포넌트에 전달됩니다.
  3. 그리고 드윗 컴포넌트는 전달받은 드윗 객체의 속성들을 가지고 각각의 드윗을 구성하게 됩니다.

드윗을 화면에 렌더하는 과정은 위와 같은 흐름으로 진행됩니다. 좋아요 데이터를 유저 객체에 저장할 경우 기존의 흐름에 좋아요 데이터만 전달하는 흐름을 추가적으로 만들어야 하는 불편함이 있었습니다.

방법2: 드윗 객체에 추가한다

이렇게 해서 결국 두 번째 방법으로 토대를 다시 잡았습니다. 두 번째 방법에서는 각각의 드윗 객체들이 좋아요를 누른 유저의 id 정보를 리스트로 저장합니다. 그리고 드윗 데이터를 화면에 불러올 때 현재 유저의 id 가 해당 드윗의 좋아요 리스트에 존재하면 좋아요를 이미 누른 것으로, 존재하지 않는다면 좋아요를 안 누른 것으로 판단합니다.

방법2를 고안한 이유?

기존의 데이터 흐름 재사용 가능

좋아요를 누른 유저 리스트를 드윗 객체에서 관리하면 리스트가 드윗 객체 안에 포함되므로 기존의 데이터 흐름을 그대로 사용할 수 있습니다. 또한 좋아요 개수 데이터와 좋아요를 누른 유저 리스트를 같은 컬렉션 내부에서 관리하기 때문에 데이터의 응집성 또한 높아지게 됩니다.

질문2: 구현에 필요한 재료들

그 다음 할 일은 좋아요 기능을 구현하기 위해 Firebase와 리액트 환경 내에서 구체적으로 필요한 자원들에 대해 정리하는 것입니다.

좋아요 버튼 및 좋아요 숫자

가장 단순한 것부터 생각했습니다. 우선 화면에 좋아요(엄지) 아이콘과 좋아요 숫자를 띄워야 합니다. 좋아요 아이콘의 경우 dwitter에서 지금까지 사용한 react-icons 을 사용할 수 있습니다. 그중 Heroicons 패키지의 엄지 디자인이 예뻐 보여 그걸로 선택했습니다. 숫자는 단순하게 p 태그로 구현했습니다.

좋아요 상태 및 개수 실시간 업데이트

좋아요 개수는 다른 사용자가 좋아요를 누를 때 실시간으로 업데이트 되어야 하고, 자신이 좋아요를 누른 드윗은 로그아웃 후 다시 들어와도 계속 좋아요가 눌려 있어야 합니다.

이 부분은 추가로 구현할 필요가 없습니다. 이미 Home.js 파일의 onSnapshot()에서 드윗 콜렉션의 데이터 변화를 감지하고 있기 때문입니다. 좋아요 관련 데이터는 드윗 객체 안에 저장되므로 좋아요 관련 데이터 역시 자동으로 업데이트됩니다.

좋아요 상태 관리

드윗 객체 안의 '좋아요를 누른 유저 리스트'와 현재 유저를 비교해서 현재 유저가 좋아요를 누른 적이 있었는지 확인해야 합니다. 그리고 좋아요 여부를 true 아니면 false 로 저장한 다음 이 값을 바탕으로 좋아요와 관련된 모든 동작이 바뀌어야 합니다. 예를 들어,

  • 좋아요 버튼을 눌렀을 때 핸들러의 동작은 좋아요 여부가 true 라면 '좋아요 취소'가 되어야 할 것이고, false 라면 '좋아요 추가'가 될 것입니다.
  • 화면 상의 아이콘은 좋아요 여부가 true라면 색이 칠해진 아이콘이어야 하고 false 라면 속이 빈 아이콘이어야 합니다.

리액트에서 제공하는 useState 후크를 통해 좋아요 상태를 컴포넌트 내부에서 관리할 수 있습니다. 핸들러 함수와 아이콘은 이 state 값을 바탕으로 조건적으로 동작하게 됩니다.

Firebase API

마지막으로 좋아요 버튼을 누르면 실행되는 콜백 함수에서 데이터베이스와 어떻게 소통할 것인지 생각해야 합니다. 우선 Firebase에 저장된 컬랙션의 도큐멘트에 접근하는 방법은 알고 있습니다.

const documentRef = DBService.collection("dwitte").doc(dwitteObj.id)

DBServicefirebase.frestore()로 생성해 다른 파일에서 불러온 Firestore 인스턴스입니다. 이렇게 하면 documentRef에 dwitte 콜렉션 중 해당 드윗의 아이디를 가진 도큐먼트의 레퍼런스가 저장됩니다.

좋아요 개수와 좋아요를 누른 유저 리스트 모두 기존 데이터를 업데이트하는 방식으로 데이터 변경이 이루어질 것이기 때문에 도큐먼트 레퍼런스의 update() 메서드를 사용하겠습니다.

실제 구현

이제 구상한 방법들을 바탕으로 실제 코드를 구현해보겠습니다.

드윗 객체에 속성 추가

우선 드윗 객체는 제출 버튼을 누르면 사용자가 입력한 폼과 기타 속성들을 바탕으로 만들어져 데이터베이스에 저장됩니다. 따라서 데이터베이스에 보낼 객체를 생성할 때 좋아요를 받은 횟수와 좋아요를 누른 유저 목록을 저장할 수 있는 속성 두 개를 새로 만들어주겠습니다.

드윗 제출과 관련된 모든 것은 DwitteFactory 컴포넌트에서 다룹니다. 사용자가 제출 버튼을 누르면 핸들러 함수인 onSubmit 이 동작하게 되는데, 여기서 만들어지는 드윗 객체를 수정해주었습니다.

좋아요를 받은 횟수는 likeCount 속성으로, 좋아요를 누른 유저 리스트는 likeUsers 속성으로 만들었습니다.

'좋아요' 여부 확인

이제 현재 접속한 유저가 어떤 드윗에 좋아요를 눌렀는지 알아야 합니다. 드윗 객체들이 화면에 렌더되는 과정은 다음과 같습니다.

  1. Home 컴포넌트에서 실시간 리스너로 드윗 객체가 담긴 배열을 받아옵니다.
  2. 드윗 객체 및 현재 유저 객체에서 필요한 정보를 빼내 Dwitte 컴포넌트에 프롭으로 전달합니다.
  3. Dwitte 컴포넌트는 마치 붕어빵 틀처럼 받은 정보를 바탕으로 하나의 드윗을 렌더합니다.

드윗 객체와 유저 객체를 받아오는 Home 컴포넌트에서 미리 현재 유저와 드윗 객체들이 가진 likeUsers 배열을 대조해서 좋아요를 눌렀는지의 여부를 부울형으로 각각의 Dwitte 컴포넌트에 전달하면 편리할 것입니다.

Home 컴포넌트가 JSX를 반환하는 부분입니다. Dwitte 컴포넌트에 isUserLike 라는 프롭으로 현재 유저의 좋아요 여부를 전달합니다. 좋아요 여부 검사는 자바스크립트의 includes() 메서드를 사용해 구현했습니다. userObjuid 값이 드윗 객체의 likeUsers 리스트에 있는지의 여부를 검사합니다.

드윗은 홈 화면만이 아니라 프로필 화면에서도 렌더되므로 Profile 컴포넌트의 JSX 반환 부분도 똑같이 수정해주었습니다.

좋아요 버튼 렌더

각각의 Dwitte 컴포넌트는 현재 유저의 좋아요 여부를 프롭으로 전달받아 isLike state 의 초기화 값으로 사용합니다.

이제 이 상태값을 함수 컴포넌트 내에서 두루두루 사용할 것입니다.

우선 좋아요 버튼이 이 상태값을 사용합니다. 좋아요 버튼은 isLiketrue 라면 색칠된 엄지 모양이어야 하고, false 라면 속이 빈 엄지 모양이어야 합니다.

이 부분을 자바스크립트의 삼항 연산자로 구현하였습니다. <HiThumbUp /><HiOutlineThumbUp /> 태그에 리액트 아이콘이 들어가게 됩니다.

좋아요 버튼 핸들러

가장 중요한 핸들러 함수입니다. 사용자가 좋아요 아이콘을 누르면 이 콜백 함수가 작동합니다. 코드를 보면서 설명하겠습니다.

전체적인 흐름은 다음과 같습니다.

만약 isLikefalse 라면:

  • 현재 드윗 도큐멘트의 likeUsers 배열에 crrentUser.uid 추가
  • 현재 드윗 도큐멘트의 likeCount 필드 값 1 증가
  • isLike true로 변경

만약 isLiketrue 라면:

  • 현재 드윗 도큐멘트의 likeUsers 배열에 crrentUser.uid 삭제
  • 현재 드윗 도큐멘트의 likeCount 필드 값 1 감소
  • isLike false로 변경

Firebase에서는 도큐멘트의 필드 값을 쉽게 변경할 수 있는 메소드를 많이 제공합니다. 그중 제가 사용한 것은

  • arrayUnion()
  • arrayRemove()
  • increment()

이 세 가지입니다.

먼저, arrayUnion() 메서드의 경우 필드가 배열일 경우 배열에 인자로 받은 아이템을 하나 추가합니다. arrayRemove() 은 그 반대의 역할을 합니다.

increment() 메서드는 숫자 필드의 값을 인자로 받은 만큼 더해줍니다.

결과

이제 모든 기능이 완성되었습니다.

  1. 사용자가 로그인 후에 홈 화면에 들어오면 드윗 객체가 불러와짐과 동시에 현재 유저의 아이디와 각각의 드윗이 갖고 있는 좋아요 리스트를 대조하여 좋아요 상태를 초기화합니다.
  2. 사용자가 좋아요 버튼을 누르면 핸들러 함수가 실행됩니다. 만약 좋아요가 눌러져 있으면 좋아요를 취소하고 그렇지 않으면 좋아요를 추가합니다.
  3. DB에 저장된 드윗 객체의 좋아요 데이터에 변화가 생길 때마다 자동으로 데이터가 업데이트되며, 컴포넌트가 리렌더링됩니다.

포스팅을 마치며

지금까지 강의에서 지시하던 대로 만들어나간 과정에 비하면 스스로 새로운 기능을 추가하는 과정은 훨씬 많은 생각과 시행착오를 겪게 만드는 것 같습니다. 특히 Firebase를 데이터베이스로 사용하기 때문에 백엔드에서 데이터를 처리하는 부분을 자유롭게 만들 수 없어 공식 문서를 항상 옆에 끼고 작업을 진행했습니다.

쉘 사용법에 대한 포스팅을 진행하다 오랜만에 재미있는 결과를 얻으니 기분이 새롭습니다. 다음에도 발전된 기능으로 포스팅하겠습니다.🙇‍♂️

profile
프론트엔드 웹 개발자를 목표로 하고 있습니다.

0개의 댓글