프론트엔드 데브코스 5기 TIL 19 - 노션클로닝(5)

김영현·2023년 10월 24일
1

TIL

목록 보기
23/129

잃어버린 노드를 찾아서

  1. 문서를 누르면, 하위문서가나온다.
    1-1. 하위문서는 display-none상태다.
    1-2. document-item이 문서고, document-list는 문서 묶음이다(래핑). 따라서 document-item의 하위문서가 document-list안에 담겨있다.
    1-3. document-itemdocument-list는 재귀적으로 구현했기에 형제노드다.
  2. 결국 하위문서를 토글하려면 nextSibling을 사용하면된다.

여기까지는 맞았다.
그래서 화살표버튼을 클릭할 시 nextSiblingdisplay-none이 토글되게 함.
여기까지는 잘 됐다.
그런데, localStorageisFolded값을 두고 렌더링시 토글상태를 유지하려했음.
그러나 초기렌더링단계에서 nextSibling이 선택되지 않는 현상 발생.

한번 알아볼까?

componentDidMount가 필요하다...

내 구조는 Nav > DocumentList > DocumentItem > DocumentList(재귀)
이 구조다.
Nav에서 문서들을 가져와 list의 상태로 넣어준다. 이때 listsetState를 사용하여 한번 리-렌더 시켜줌.
list에서 setState가 발생하면 당연히 item에서도 setState를 발생시켜 리-렌더시켜줌.

아하! 일단 가드용 초기값이 있고, setState값이 들어와야 상태가 존재하는구나
여기까지는 이해 완료.
하지만 nextSibling을 사용하는 item의 자식컴포넌트에서는 잘 작동한다....

해결중

크롬 디버거로 찍어보니, 재귀적 렌더링때문에 아직 요소들이 DOM에 붙지 않아 선택할 수 없었다.
따라서 render()메소드 맨 마지막에 nextSibling을 선택하는 로직을 넣었더니 잘 작동하는 듯 싶다가도...마지막 문서에만 적용된다. 흠...

해결 완료

구조를 조금 더 바꿧다.
DocumentList는 그냥 DocumentList들을 렌더링하기위한 일종의 래퍼.
DocumentItem은 문서 그 자체. 또한 내부에서 DocumentItem들을 재귀적으로 렌더링한다.
따라서 item내부에서 item관련 로직을 처리할수 있게되었다!
item조작을 item내부에서 할수있음!!!


드래그로 리사이즈 구현하기

e.target에 대한 메소드가 많이 필요할 것 같다.

  • e.target.clientX: 타겟 왼쪽을 기준으로 0부터 시작하는 X좌표
  • e.target.offsetWidth: 타겟의 너비
  • cursor: col-resize:커서 열 리사이즈 가능하게(가로로)

이 세 메소드로 구현하였다!

document.addEventListener("mousedown", (e) => {
    //마우스 다운시점의 시작 x좌표와, nav바의 너비를 저장해둠.
    const startX = e.clientX;
    const initialWidth = $nav.offsetWidth;
    //마우스가 10px전후로 넘어가면 별로다
    if (Math.abs(startX - initialWidth) > 10) return;
    const onMouseMove = (e) => {
      const newX = e.clientX;
      const delta = newX - startX;
      const calculatedWidth = Math.max(minNavWidth, initialWidth + delta); // 최소 너비를 250px로 설정
      //최대너비는 500px
      if (calculatedWidth >= maxNavWidth) {
        $nav.style.width = `${calculatedWidth}px`;
        //크기를 더 키우려하면(클릭 시점보다 움직인 좌표가 양수면)
        if (startX < newX) {
          onMouseUp();
        }
      } else {
        $nav.style.width = `${calculatedWidth}px`;
      }
    };
    const onMouseUp = () => {
      document.removeEventListener("mousemove", onMouseMove);
      document.removeEventListener("mouseup", onMouseUp);
    };
    document.addEventListener("mousemove", onMouseMove);
    document.addEventListener("mouseup", onMouseUp);
  });
  document.addEventListener("mousemove", (e) => {
    const startX = e.clientX;
    const initialWidth = $nav.offsetWidth;
    if (Math.abs(startX - initialWidth) > 10) {
      e.target.classList.remove("resize-cursor");
      $nav.classList.remove("thick-border");
    } else {
      e.target.classList.add("resize-cursor");
      $nav.classList.add("thick-border");
    }
  });

뭔가 살짝 맘에 안드는데...마우스 무브 이벤트를 2번이나 거는게 기분나쁘다.


옵저버 패턴 구현하기

옵저버패턴을 이용한 구독-알림시스템을 만들거다.

일단 구독하면 상태 변경알림이 와야한다
내게 필요한건 문서를 생성하거나 수정하면 getDocuments, getDocument를 다시 호출해야함
따라서 문서를 수정하는 일이 생긴다면, 알림을 이용하여 위 메소드를 호출해보자

일단 작은거 부터

  1. 구독자들을 저장해야한다
  2. 구독, 구독취소 기능이 있어야 한다.
  3. 구독자들에게 알려주면, 구독자들이 보낸 메소드를 호출한다.

이를 토대로 살짝 만들어보면

class Observer {
  constructor() {
    this.subscribers = [];
  }
  subscribe(observerCallback) {
    this.subscribers.push(observerCallback);
  }
  unsubscribe(observerCallback) {
    this.subscribers = this.subscribers.filter(
      (subscriber) => subscriber !== observerCallback
    );
  }
  notify() {
    this.subscribers.forEach((subscriber) => subscriber());
  }
}
export default Object.freeze(new Observer());

요래된다.
인스턴스를 생성하고 바로 내보내줘서 싱글톤(인스턴스가 무조건 하나만)으로 사용하게끔 할것이다.
계속 새로운 인스턴스가 생성되면 안되잖아?

이걸 이용하여 DocumentPage에서 글이 수정되면, 일단 문서리스트를 가져오는 NavPage에서 다시 문서리스트를 가져오게끔 할것이다.


느낀점

디자인 패턴이라고 막 엄청 어려운건 아니었다.
결국 방법론이고 작은것 부터 만들어보면 쉽게 이해할 수 있었다.
또한 문제와 마추지지 않는 날이 없다. 따라서 문제를 잘 정리하고 해결해서 기록해놓는게 정말 중요할 것 같다.

profile
모르는 것을 모른다고 하기

0개의 댓글