[ 자바스크립트 스터디 ] mission1 - 코드리뷰를 통해 배운 점들

Frontend Dev Diary·2020년 11월 5일
0

최근에 자바스크립트 기초 역량을 다지기 위해 프론트엔드 개발을 위한 자바스크립트 스터디(feat. VanillaJS)를 신청했다. 스터디 과제를 진행하면서 처음으로 제대로 된 코드리뷰를 받아볼 수 있었다😀
첫주 차 미션은 생각보다 간단해서 금방 했지만, 코드리뷰를 받고보니 내 코드에 문제가 많았다는 것을 알게 되었다...

1. 로직을 분리하기

😑 변경 전

처음에 작성했었던 validation 관련 함수이다.
초기화와 관련된 validation과 data 유효성 검사 부분이 뒤섞여있다. 주석의 의미나 error 메세지도 실제 검증하려는 것과는 다르다.

this.isValid = function({ data, listID }) {
  if (!Array.isArray(data)) {
    throw new Error("Data is not Array");
  }
  if (listID && !(this instanceof Todo)) {
    throw new Error("Keyword new is be omitted");
  }
  // 데이터 내용이 이상한 경우
  const invalidArray = data.filter(data =>
    typeof(data.text) !== "string" || typeof(data.isCompleted) !== "boolean")
  if (invalidArray.length > 0) {
    throw new Error("Invalid data is included");
  }
  // id가 잘못된 경우
  if (listID && !document.querySelector(`#${listID}`)) {
    throw new Error("Id is not valid");
  }

🙂 변경 후

초기화와 관련된 부분과 데이터 유효성 검사 부분을 분리했고, 적절한 에러 메세지와 주석으로 바꿨다!
한 함수 안에 여러 일들을 하려고 할 필요가 없다. 그렇게 해봤자 코드의 가독성만 떨어질 뿐...

this.checkInitialization = function(id) {
  if (!(this instanceof Todo)) {
    throw new Error("'this' is not instance of Todo");
  }
  // id에 해당하는 DOM이 없는 경우
  if (id && !document.querySelector(`#${id}`)) {
    throw new Error("Id is not valid");
  }
}

this.checkTodoListData = function(data) {
  if (!Array.isArray(data)) {
    throw new Error("Data is not Array");
  }
  // 데이터 내용이 이상한 경우
  const invalidArray = data.some(data =>
    typeof(data.text) !== "string" || typeof(data.isCompleted) !== "boolean");
  if (invalidArray) {
    throw new Error("Invalid data is included");
  }
}    
    

😄 더 잘게 쪼개기

나는 저정도 정도만 분리했지만, 스터디 세션에서 재사용성을 위해서는 더 쪼개는게 좋다는 것을 알게 되었다.

this.validation = (state) => {
  useNewKeyword(this)
  useArrayState(state)
  checkTarget(this.$target)
  checkTypes(
    state,
    ({ text, isCompleted }) =>
    typeof text === 'string' && typeof isCompleted === 'boolean'
  )
}

2. Array의 map, reduce, filter 등을 적절하게 이용하기

😑 변경 전

filter를 이용해 array의 처음부터 끝까지 돌고, 길이가 0 이상인 경우 에러 메세지를 줬다.

const invalidArray = data.filter(data =>
  typeof(data.text) !== "string" || typeof(data.isCompleted) !== "boolean")
if (invalidArray.length > 0) {
  throw new Error("Invalid data is included");
}

😄 변경 후

filter는 invalid한 data를 만나더라도 array의 모든 item을 순회한다는 단점이 있다. invalid한 data가 하나만 있더라도 invalid한 array이기 때문에, some을 사용하는 것이 더 적절하다.

const invalidArray = data.some(data =>
  typeof(data.text) !== "string" || typeof(data.isCompleted) !== "boolean");
if (invalidArray) {
  throw new Error("Invalid data is included");
}

더 공부해서 상황마다 적절한 메서드를 쓰기 위해 노력해야겠다.
map, reduce, filter를 유용하게 활용하는 15가지 방법

3. DOM 변수명을 저장하는 방법

이 부분은 코드리뷰를 통해 알게된건 아니고, 스터디 세션을 통해 배운 내용이지만 같이 정리하면 좋을 것 같아서 넣었다.

😑 내가 작성했던 코드

 function Todo(data, id) {
   this.checkInitialization = function(id) {
     // id에 해당하는 DOM이 없는 경우
     if (id && !document.querySelector(`#${id}`)) {
       throw new Error("Id is not valid");
     }
   }
   ...

이런 방법은 변수명이 id로 제한되어 있어 class 값으로 참조하고 싶을때는 애매하다는 단점이 있다.

😄 더 좋은 방법

target을 파라미터로 직접 넣어주면 함수 내부에서는 id/class인지를 신경 쓸 필요가 없다.

TodoList.js

function TodoList($target, initialState) {
  this.state = initialState
  this.$target = $target
  ...

index.js

const todoList1 = new TodoList(document.querySelector('#todo-list'), data)

validation.js

export const checkTarget = ($target) => {
  if (!$target) {
    throw new Error('target이 올바르지 않습니다.')
  }
}

4. this

this를 붙이고 만드는 함수와 그렇지 않은 함수가 어떻게 다를까? 글로는 여러 번 읽어보았지만 막상 this를 사용하자 개념이 헷갈렸다. 이 부분에 대해서는 Lucas님과 Jeffrey님이 친절하게 답변해주셨다.

function A() {
  this.print = function () {
    console.log(this); //Todo
  };

  print2 = function () {
    console.log(this); //Window
  };

  print3 = function() {
    console.log(this)
  }.bind(this)

  this.print()
  print2()
  print3()
}

이 함수를 new를 이용해 initialize하게 되면 다음과 같은 결과를 얻게 됩니다.

const a = new A()
// A {print: ƒ}
// Window {window: Window, self: Window, document: document, name: "", location: Location, …}
// A {print: ƒ}

이 이유는 arrow function이 아닌 형태로 함수를 선언할때 this의 context를 재정의하기 때문에 일어나는 일입니다.

  • 첫 번째 print의 경우 this.print()로 실행되기 때문에 print method의 context가 initialize된 A의 instance로 결정되어, A가 출력됩니다.
  • 두 번째 print2의 경우 실행 시점의 this의 context가 특별히 지정되지 않았기 때문에 window 객체가 context로 결정되어 window가 출력됩니다.
  • 세 번째 print3의 경우 method 형태는 아니지만, Function.prototype.bind(this)함수가 실행되어 해당 함수의 context가 initialize된 A의 instance로 결정됩니다. 그렇기 때문에 A가 출력됩니다.

또한,

const a = new A()

a.print() // A
a.print2() // Uncaught TypeError: a.print2 is not a function

this를 이용해 선언한 method는 A의 instance에서 접근이 가능하다. 새로운 객체의 render 프로퍼티에 함수를 대입하기 때문이다.
반면 내부 함수는 return하기 전 까지는 접근할 수 없다. 함수를 지역변수(컴포넌트 함수 내부)에 가두기 때문.

profile
성장하는 프론트엔드 개발자

0개의 댓글