[TIL] 2022-04-19 / 22일차

미음제

·

2022. 4. 20. 16:52

 

오늘 배운 내용

 

하루가 지나서 적는 TIL. 어제 작성하려 했는데 정리하다 보니 하루가 다 지났다..

 

글을 작성하는 오늘, 노션 클로닝 프로젝트를 PR해야 하기 때문에 보너스 요구사항에 대한 기능 구현은 힘들 것 같아 구조를 개선하고자 했었다. 그러나 처음 과제를 시작했을 때(이미 한번 리셋을 했음에도) 컴포넌트 구조를 잘 못 짠 것 같아 리팩터링이 쉽지 않다(개선점이나 엣지 케이스를 잘 파악하지 못한 것도 있다). 처음 과제를 시작하기 전에 컴포넌트 설계를 미리 해두고 시작했으면 어땠을까 하는 아쉬움도 있다. 그렇다고 손 놓고 가만히 있을 순 없기 때문에 어떻게 개선을 해야 할지 파악하기 위해 현재까지 작성한 상태로 컴포넌트 구조를 그려보고 파악해 보았다.

 

전체 구조 1

 

 

전체 구조 2

 

 

App.js

 

하위 컴포넌트들

 

구현을 하기 전에 구조를 그리지 않고, 어느정도 구현을 마친 후(제멋대로지만..) 구조를 그리고 나니 내가 작성한 코드지만 흐름을 파악하는데 어려움이 많았다. 어떤 경우에 어떤 컴포넌트가 상태가 변화되고 그에 맞는 동작이 실행되는지 파악하기 어려웠었다. 컴포넌트 분리, 구조 설계 등에 대한 중요성을 다시 느끼게 된다.

 

전체적인 구조를 그려놓고 흐름에 대해 파악하다 보니 가장 먼저 든 생각은 "깔끔하지 못하다"이다. 주먹구구식으로 한 단계 한 단계 기능 구현을 하다 보니 당연한 것인지..? 컴포넌트를 생성할 때 두 개의 하위 컴포넌트만(listcontainer와 editorcontainer) 생각하고 진행해서 생긴 문제 같다. 하위 컴포넌트를 미리 설계할 수 있는 방법에 대한 고민이 많아졌다. 그리고 이렇게 가장 많이 느꼈던 부분이 SPA를 구현하는 부분이다. route() 함수로 url을 관리하고 있는데, url이 바뀔 때마다 적절하게 렌더링 하는 게 어려운 것 같다. 원하는 결과가 나오지 않아 응급조치를 취해 눈앞에서 보기에는 동작하는 것처럼 보이지만 가능성 있는 오류에 대해 고민해보지 않고 작성했다. 이 부분도 미리 생각할 수 있는 방법은 무엇인지 생각이 많아진다.

 

그래도 이렇게 구조를 그리고 어떻게 변화하는지 파악하기 위해 내 코드를 다시 뜯어 보면서 불필요한 코드를 찾아 제거하는 소소한 수확이 있었다(console.log()를 통한 디버깅을 벗어날 수 없다😒). 계속해서 언급이 되지만 컴포넌트 구조를 미리 설계하지 않고 될 대로 되라는 식의 무지성 코딩 때문에 초기 state로 불필요한 것들이 많았다.

 

1. api 요청 방어 코드를 작성할 때

 

// src/api/api.js

if (!res.ok) {
  restate('/');
  throw new Error('API 처리 중 에러 발생');
} else {
  return await res.json();
}

 

요청 응답이 올바르지 않은 경우, 루트로 history api를 대체하고 에러를 던지는 코드를 작성했다. 이렇게 작성하고 루트로 돌아올 때, editorcontainer에 남아 있던 editor와 documentlist가 그대로 렌더링 되는 것을 발견했다. 콘솔에서 확인 하니, 루트로 돌아왔을 때 editorcontainer의 상태는 undefined가 되어있는데 이에 맞는 코드가 없어서 해당 문제가 발생했다. 

 

// src/Editor.js

this.setState = (nextState) => {
  if (nextState !== undefined) { // 추가
    this.state = nextState;
    $editor.style.display = '';
    $('[name=title]').value = this.state.title;
    $('[name=content]').value = this.state.content;
  }
};

 

this.render = () => {
  if (this.state === undefined) return; // 추가
  const childDocuments = this.state.documents;
  if (childDocuments.length > 0) {
    $documentLists.innerHTML = `
		// 생략
      `;
  } else {
    $documentLists.innerHTML = '';
  }
};

 

이렇게 state가 없는 경우 렌더링을 다르게 하도록 수정해 해결할 수 있었다(최선의 방법인지는 모르겠다.. 이런걸 파악하는 게 너무 어렵다)

 

2. 불필요한 state 삭제

 

컴포넌트를 생성할 때 state 값으로 초기화 된 initialState를 넘겨주는데, 무지성으로 작성한 부분들이 많다(아직 발견하지 못한 부분들이 있을 것 같다). 열심히 콘솔 창에 로그를 찍어가며 state가 변경될 때마다 살펴보고 있었는데 editorcontainer에 id 값 외의 것들은 필요가 없었다. 편집기에 대한 상위 컴포넌트가 id 값만 가지고 있고, 편집기와 하위 Documents를 나타내는 두 컴포넌트는 해당 id 값에 맞는 title, content, documents를 요청받으면 되는데 상위 컴포넌트에서 title과 content를 초기 state로 갖도록 작성되어 있었다(한 번도 사용되지도 않는데). 무식한 방법이지만 로그를 찍어가며 흐름을 파악하며 찾아낸 소소한 결과물..

 

const editorContainer = new EditorContainer({
    $target,
    initialState: {
      postId: 'new',
      /*post: {
        title: '',
        content: '',
      }, 필요 없는 부분 */
    },
 // 이하 생략

 

3. 불필요한 로직 삭제

 

간단한 에디터를 만드는 강의를 들을 때, 사용자의 입력값을 localStorage에 임시로 저장해 두고 API 요청을 처리하지 않은 상태에서 다시 편집 상태로 돌아오면 서버에 올라간 데이터 보다 localStorage에 남아 있는 데이터가 최신일 경우 해당 데이터를 불러오는 것을 따라 구현하기 위해 localStorage 관련 내용을 작성해 두었다. 나중에 그 부분에 대해 보완해 완성하려 했으나 부족한 탓에 구현하지 못했고 자연스럽게 잊혀 갔다. 잊힌 코드는 불필요한 내용이었을 뿐이다. 'PUT' 요청에 대한 에러는 아직 발견하지 못해 생각하지 못하고 넘어갈 곳이었는데 구조를 그리며 캐치했고 불필요하다고 판단해(아직까지는 구현하기 힘들어 보인다) 삭제할 수 있었다.

 

// 변경 전 src/EditorContainer.js

const currentPost = getItem(`temp-post-${this.state.postId}`, {
  title: '',
  content: '',
});

let timer = null;
const editor = new Editor({
  $target: $editorContainer,
  initialState: currentPost,
  onEditing: (post) => {
    if (timer !== null) {
      clearTimeout(timer);
    }
    timer = setTimeout(async () => {
      setItem(`temp-post-${this.state.postId}`, {
        ...post,
        tempSaveDate: new Date(),
      });
      await request(`/documents/${post.id}`, {
        method: 'PUT',
        body: JSON.stringify({
          title: post.title,
          content: post.content,
        }),
      });
      updatePostsList();
      removeItem(`temp-post-${this.state.postId}`);
    }, 2000);
  },
});

 

// 변경 후 src/EditorContainer.js

/* const currentPost = getItem(`temp-post-${this.state.postId}`, {
  title: '',
  content: '',
}); 삭제 */

let timer = null;
const editor = new Editor({
  $target: $editorContainer,
  initialState: { // 수정
      title: '',
      content: '',
    },
  onEditing: (post) => {
    if (timer !== null) {
      clearTimeout(timer);
    }
    timer = setTimeout(async () => {
      /* setItem(`temp-post-${this.state.postId}`, {
        ...post,
        tempSaveDate: new Date(),
      }); 삭제 */
      await request(`/documents/${post.id}`, {
        method: 'PUT',
        body: JSON.stringify({
          title: post.title,
          content: post.content,
        }),
      });
      updatePostsList();
      /* removeItem(`temp-post-${this.state.postId}`); 삭제 */
    }, 2000);
  },
});

 

부족한 점 & 느낀 점

 

외출 후에 돌아와 보니 요한팀 채널에 팀원분들이 남아 계셨다(멘토님이 있었다가 가셨다는데 멘토님은 보지 못했다😅). 노션 클로닝 프로젝트 어느 정도 진행하고 완성했는지, 어떤 부분이 어려웠는지에 대한 이야기를 간단하게 나눴었다. 다들 비슷한 고민인 것 같다. 추가적인 기능 구현을 하려면 많이 손봐야 하고(컴포넌트 추가하면서 다른 컴포넌트는 건드리지 않게 혹은 상태 관리) 처음 구조를 짜는 것에 대한 어려움이 있다는 식의 얘기였다. 나름 위안이 되면서(나만 그런 게 아니구나) 한편으론 걱정도 되었다(비슷한 느낌과 결론이더라도 부족한 나의 완성도와 팀원들의 완성도 차이가 있을 테니). 바닐라 자바스크립트로 구현하는 게 어려운 게 맞다는 이야기도 들었지만, 다른 분들은 곧잘 해서 아직까진 걱정이 더 큰 것 같다. 프레임워크나 라이브러리를 이용하지 않고 간단한 과제를 진행할 때 구조 설계나 파악이 힘들면 추후에 프레임워크나 라이브러리를 이용할 때 문제가 되지 않을까(무지성 돌격..) 싶다. 다른 과제에 대한 피드백 반영도 꾸준히 잘해야 하지만 특히 이번 프로젝트는 끝날 때까지 꼭꼭 씹어서 소화해야 할 것 같다.

반응형