⚠️ 이 글에 틀린 정보가 숨어있을 확률이 높다..

 

 

무한 대댓글을 구현하는게 어렵다고 해서, 어떻게 구현할 수 있을까 생각을 해봤다.

express + Mongo db 의 입장에서만 생각해봤다!!

일단 이런 방식의 계속해서 깊어지는 댓글을 구현하는 방법을 중심으로 떠올려봤다.

 

각 깊이별로 차례차례 생각해보았다.

진짜 이 순서대로 생각해본 거다!

 

Depth 0

post에 직접적으로 달리는 comment는 post._id 를 통해서 삽입이 된다.

post._id를 통해서 기본적인 대댓글 없는 댓글은 구현이 가능하다. (나도 방금 해봤다.)

Depth 1

그러면 댓글에 댓글을 다는, 대댓글은 어떻게 해야할까?

comment._id를 통해서 커멘트에 커멘트를 다는 방식으로 구현하면 될 것 같았다.

child comment 배열을 통해서 해당 댓글에 달린 대댓글을 전부 추적할 수 있다.

Depth 무제한

https://granora2019.tistory.com/entry/NodeJS와-MongoDB로-SNS-만들어보기-userment-댓글대댓글-라우터

 

NodeJS와 MongoDB로 SNS 만들어보기 - user_ment (댓글/대댓글 라우터)

2019/11/14 - [띡딱똑띡 개발자 Hanna] - NodeJS와 MongoDB로 SNS 만들어보기 - user_postingPlus (게시글 라우터2) NodeJS와 MongoDB로 SNS 만들어보기 - user_postingPlus (게시글 라우터2) 2019/11/14 - [띡딱똑띡 개발자 Hanna]

granora2019.tistory.com

https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEQW7z%2FbtqzLEptmPu%2Fl1eTk3YW6uk8YtgWb1C930%2Fimg.png

위 게시물의 그림을 보면서 도움 받았다.

솔직히 그림만 보고 텍스트는 읽지 않았다.

 

깊이 1의 유기적인 데이터들을 생각하다보니,

링크드 리스트 + 트리 느낌으로 구현할 수 있을 것 같았다.

 

Depth는, parent node의 depth에서 1을 더해주면 쉽게 구할 수 있다.

 

게시물부터 depth를 명시해줘서 (포스트는 안 해줘도 되고) 모든 comment는 자신의 parent node의 depth를 따라가면 될 것 같다.

프론트에선 백엔드에서 미리 계산해준 depth를 통해서 들여쓰기와 같은 방법으로 쉽게 무한 대댓글을 구현할 수 있다.

백엔드에서의 구현은 이 정도면 된다..

 

그럼 depth를 명시해주지 않으면 프론트에선 어떻게 해야할까?

왜 프론트에서 DB에 접근하지 않을까? 를 작성하며 보안 측면에서 생각하다보니, 배포 때의 환경변수 관리가 궁금해졌다.

당장 .gitignore 파일에도 .env 를 등록해두는데, 배포할 땐 올릴 리가 없고… 어떻게 할까 싶었다.

 

찾아보니까, Vercel, Netlify 기준, 프로젝트 세팅에서 직접 입력해줄 수가 있었다.

당연하지만 key, value 각각 입력할 수 있는 방식이었다.

 

다음주에 카테캠에서 만든 것 배포하게 될텐데, 갑자기 떠올라서 찾아봤다.

백엔드를 구현하다보니, 결국 프론트에서 DB까지의 징검다리 역할을 하는 것만 같았다.

그리고 모든 작업은 프론트에서 가능해보였다.

 

그래서 궁금해졌다.

왜 프론트에서 DB에 직접적으로 접근하지 않을까?

 

결과부터 말하자면, 전부 가능한 것이었다. 그러나 분리되어야만 하는 이유가 있었다.

  • 보안과 관련된 것들이 전부 사용자에게 노출된다.
  • 알고리즘이 노출된다.
  • DB 역시 노출되어서, 다른 사람들이 마음대로 접근할 수 있다.
  • 서버에서 작업들을 분산적으로 처리하지 않고, 브라우저로 한 번에 쏴서 모든 역할을 전가한다면 브라우저의 부담이 엄청나진다. 등등 …

생각해보면 1, 2, 3번은 간단히 생각해봐도 떠올릴 수 있는 것들이었다.

근데 보안을 생각하다보니, 배포할 땐 환경변수를 어떻게 관리하는지 궁금해졌다.

오늘 찾아보고 글 작성해서 링크 걸어야겠다.

 

Is it possible to directly connect frontend to the database?

 

Is it possible to directly connect frontend to the database?

I came across this joke the other day. Someone on the internet was asking why do we need a backend, just connect the frontend to the database. It seemed funny at first, but really is it possible to

stackoverflow.com

Why do we need a back-end in web development? Can't the front-end directly send requests to the database?

 

Why do we need a back-end in web development? Can't the front-end directly send requests to the database?

Answer (1 of 6): While you could theoretically send requests directly from the front-end to the database that’s something of a gross oversimplification. Let’s look at some of the problems: Security is the biggest and it alone should stop the line of th

www.quora.com

 

카카오 쇼핑하기를 구현하면서, 체크박스를 어떻게 관리하는 것이 효과적일까? 하는 생각이 들었다.

체크박스를 사용하는 상황은 다음과 같았다.

- 전체 동의의 체크 상태에 따라서 나머지 2개도 체크가 적용 / 해제
- 나머지 2개의 체크 상태에 따라서 전체 동의도 체크 적용 / 해지

- 전체 동의가 되어있지 않다면 결제하기 버튼 클릭 불가

대충 이 정도의 구현이 필요한 상황이었다.

 

구현만 놓고 보면 매우 간단하지만, 어떻게 구현하는 게 더 효과적일까 하는 궁금증이 있었다.

 

첫 번째로 떠올린 것은 다음과 같았다. 

  const checkAllRef = useRef();
  const checkPaymentRef = useRef();
  const checkPrivacyRef = useRef();

  const handleCheckAllChange = () => {
    const checkAllChecked = checkAllRef.current.checked;
    checkPaymentRef.current.checked = checkAllChecked;
    checkPrivacyRef.current.checked = checkAllChecked;
  }

  const handleCheckChange = (e) => {
    checkAllRef.current.checked = checkPaymentRef.current.checked && checkPrivacyRef.current.checked;
  }
  
  /* 결제 버튼 */
  const handleOnClick = async () => {
    if(checkAllRef.current.checked === false) {
      alert("약관에 동의해주세요.");
      return;
    }
    try {
      const res = await orderProducts();
    } catch (e) {
      alert('장바구니가 비어있는지 확인해주세요');
    }
  }
  
  /* ... */
  
  <div className="border">
    <div className="font-bold text-xl border-b py-5 px-3">
      <input type="checkbox" ref={checkAllRef} id="checkAll" onChange={handleCheckAllChange} />
      <label htmlFor="checkAll">전체동의</label>
    </div>
    <div className="p-3">
      <input type="checkbox" ref={checkPaymentRef} id="checkPayment" onChange={handleCheckChange} />
      <label htmlFor="checkPayment">구매조건 확인 및 결제 진행 동의</label>
    </div>
    <div className="p-3">
      <input type="checkbox" ref={checkPrivacyRef} id="checkPrivacy" onChange={handleCheckChange} />
      <label htmlFor="checkPrivacy">개인정보 제3자 제공 동의</label>
    </div>
  </div>

useRef로 요소를 하나하나 찍어와서 관리하는 방법이다.

체크박스 변경과정에서 리랜더링은 이뤄지지 않는다.

당연히 잘 작동했으나, 체크박스 하나가 더 늘어난다면 추가해야 할 코드가 꽤 늘어난다는 문제가 있다. (handleCheckChange, handleCheckAllChange가 더러워질 수 있다.)

 

그래서 확장성에 좋은 다른 방법을 떠올려봤다.

const [checkboxes, setCheckboxes] = useState([
    { id: "checkAll", label: "전체동의", checked: false },
    { id: "checkPayment", label: "구매조건 확인 및 결제 진행 동의", checked: false },
    { id: "checkPrivacy", label: "개인정보 제3자 제공 동의", checked: false },
    // 필요에 따라 체크박스 추가 가능
  ]);

  const handleCheckAllChange = (event) => {
    const checkAllChecked = event.target.checked;
    setCheckboxes((prevCheckboxes) =>
      prevCheckboxes.map((checkbox) => ({ ...checkbox, checked: checkAllChecked }))
    );
  };

  const handleCheckChange = (event) => {
    const { id, checked } = event.target;
    setCheckboxes((prevCheckboxes) =>
      prevCheckboxes.map((checkbox) => (checkbox.id === id ? { ...checkbox, checked } : checkbox))
    );
  };

  const handleOnClick = async () => {
    if (!checkboxes[0].checked) {
      alert("약관에 동의해주세요.");
      return;
    }
    try {
      const res = await orderProducts();
    } catch (e) {
      alert("장바구니가 비어있는지 확인해주세요");
    }
  };
  
  /* ... */
  
   <div className="border">
    <div className="font-bold text-xl border-b py-5 px-3">
      <input
        type="checkbox"
        id={checkboxes[0].id}
        checked={checkboxes[0].checked}
        onChange={handleCheckAllChange}
      />
      <label htmlFor={checkboxes[0].id}>{checkboxes[0].label}</label>
    </div>
    {checkboxes.slice(1).map((checkbox) => (
      <div key={checkbox.id} className="p-3">
        <input
          type="checkbox"
          id={checkbox.id}
          checked={checkbox.checked}
          onChange={handleCheckChange}
        />
        <label htmlFor={checkbox.id}>{checkbox.label}</label>
      </div>
    ))}
  </div>

첫 번째 방법과 작동 방식은 거의 유사하다.

 

체크박스 자체를 상태로 관리한 점이 좋았다.

체크박스를 컴포넌트로 바꿔서 상태의 초기값을 props로 넘겨줘서 관리하기에 쉬워지는 장점이 있었다.

반복되는 map 함수에서 가독성이 아주 살짝 떨어질 수 있다는 단점이 있지만, 항목이 늘어날 경우에도 대응이 매~~우 쉽다는 장점이 있다.

그러나 리랜더링이 매번 이뤄진다.

 

나는 첫 번째 방법으로 완성했다.

결제 과정에서 체크해야할 항목은 딱히 변화하지 않을 것 같다는 것이 이유였다.

 

내 결정이 옳았는지는 모른다.

이번주 코드리뷰에서 멘토님께 여쭤보는 편이 좋을 것 같다.

+ Recent posts