[프로그래머스]JS 코드 리뷰 스터디 1주차

feelslikemmmm·2022년 7월 14일
0

javascript

목록 보기
37/37
post-thumbnail

일이 바쁘다는 핑계로 개인 공부를 미뤄왔던 자신을 반성하며 강의를 뒤적거리던 도중

최근에 프로그래머스 강의 목록을 보다가 코드 리뷰 스터디가 있길래 관심이 생겨 진행하게 되었습니다

회사에서는 주로 React, Next js 를 사용하고 있는데 일을 하면서 드는 생각중 하나가

어떤 프레임워크 라이브러리를 사용하던 기본기가 중요하구나 라는 생각이였습니다.

그래서 자바스크립트 관련 강의를 찾게되었고

혼자서 공부하는것보다는 피드백도 받고 다른 개발자분들과 소통도 할 수 있을 것 같다는 생각에

혼자 공부하는 인강보다는 코드 리뷰 스터디에 조금 더 흥미가 생겼습니다

제가 참여하고 있는 스터디는 매주 수요일 8시에 라이브 세션을 하고 한주 동안 진행할 미션을 받게 되는데요

7월 6일 첫번째 미션을 받고 한주가 지나서 회고를 올려봅니다

회고는 이렇게 진행됩니다

  1. 미션에 대한 설명
  2. 미션을 구현한 코드와 구현한 코드에 대해서 받은 피드백
  3. 피드백을 받고 리팩토링 해 본 코드

1주차 미션

1주차 미션은 todo list 컴포넌트 만들기였습니다

미션은 필수 구현사항 / 보너스 구현사항 으로 나누어져 있습니다

필수 구현사항

  • function style의 문법, class style의 문법 어느 것을 사용해도 좋습니다.
  • new 키워드를 통해 생성해서 사용됩니다.
  • 파라메터로 아래와 같은 형태의 data를 넘겨받습니다.
let data = [
  {
    text: 'JS 공부하기'
  },
  {
    text: 'JS 복습하기'
  }
]
  • const todoList = new TodoList(data);와 같은 형태로 파라메터를 넘기고, 생성해서 사용합니다.

  • 해당 컴포넌트에 render 함수를 작성합니다.
    이 함수는 파라메터로 넘겨받은 data를 순회하며 각 배열첨자의 text를 html string으로 만든 뒤, todo-list라는 id를 가진 div에 innerHTML을 이용해 렌더링 되도록 합니다.
    이 함수는 별도의 파라메터 없이 todoList.render() 형태로 실행되도록 만듭니다.

필수 구현사항 구현해보기

  <div id="todo-list"></div>
  <script>
  
    let data = [
      {
        text: 'JS 공부하기'
      },
      {
        text: 'JS 복습하기'
      }
    ]

  function Todo(data) {

    this.data = data;
    
    this.render = function() {
      const todoList = document.querySelector('#todo-list');
      todoList.innerHTML = this.data.map((item) => `${item.text}`).join('');
    };

  }

  const todoList = new Todo(data);
  todoList.render();

  </script>

보너스 구현사항 (다중 컴포넌트)

  • html
<div id="todo-list"></div>

외의 다른 div를 두 개 더 html 코드에 만듭니다.
div의 id는 적당한 이름으로 지어주세요.

  • javascript
var data = [{ ... }]

외에 todo를 담고 있는 array data를 두 개 더 만듭니다.
todo의 내용은 본인의 현재 todo를 담아서 넣으면 더 좋겠죠?
TodoList 컴포넌트를 총 세 개 만듭니다. 첫 번째 컴포넌트에는 제가 넣어둔 data와 #todo-list에 렌더링하고, 나머지 두 개는 여러분이 추가하신 div + data를 활용해서 만들어주세요.

보너스 구현사항 (is Completed 처리)

  • data의 각 object에 text외에 isCompleted 라는 필드를 추가합니다.
  • 해당 값은 true, 혹은 false 값을 지정해주세요.
let data = [
  { 
    text: '코딩하기',
    isCompleted: true
  },
  {
    text: '집안 청소하기',
    isCompleted: false
  }
]
  • TodoList 컴포넌트 내에 text 렌더링 시, isCompleted 값이 true인 경우
<s> 

태그를 이용해 삭선처리를 해주는 걸 추가합니다.

보너스 구현사항 (set State)

TodoList 함수에 setState(nextData)라는 함수를 만듭니다.

  • 이 함수는 해당 컴포넌트 초기 생성 시 넘겼던 data 파라메터를 nextData로 대체하고

  • 컴포넌트를 다시 렌더링합니다.
    해당 함수를 추가한 뒤, new TodoList(...) 하는 코드 이후에 해당 컴포넌트의 인스턴스에 todoList.setState(새로운 배열) 형태로 데이터만 다시 넣었을 때 컴포넌트가 다시 렌더링 되도록 작성해주세요.

  • setState 함수 호출 후 다시 todoList.render()를 호출하는 것이 아니라, setState 함수 내에서 화면을 다시 렌더링하는 것까지 처리해야 합니다.

보너스 구현사항 구현해보기

<body>

  <div id="today"></div>
  <div id="tomorrow"></div>
  <div id="afterTomorrow"></div>

  <script>
    const ERROR_MESSAGE = {
      dataDefind: 'data is not defind',
      is_array: 'data type is not array',
      type_check: 'type error'
    }

    const today = [
      {
        text: 'JS 공부하기',
        isComplete: true
      }
    ];

    const tomorrow = [
      {
        text: '청소하기',
        isComplete: false
      }
    ];

    const afterTomorrow = [
      {
        text: '빨래하기',
        isComplete: false
      }
    ]

  function validate(data) {
    if(data === null || undefined) throw new Error(ERROR_MESSAGE.dataDefind);

    if(!Array.isArray(data)) throw new Error(ERROR_MESSAGE.is_array);

    data.map((item) => {
      if(typeof item.text !== 'string' || typeof item.isComplete !== "boolean") throw new Error(ERROR_MESSAGE.type_check)
    })

    return data;
  }


  function CreateTodoList(data) {
    this.data = validate(data);
    
    this.setState = function(nextData) {
      validate(nextData);
      this.data = nextData;
      this.render();
    };
    
    this.render = function(id) {
      const todoList = document.querySelector(`#${id}`);
      todoList.innerHTML = this.data.map((item) => item.isComplete ? `<strike>${item.text}</strike>`: `${item.text}`).join('');
    };
  }

  const todayList = new CreateTodoList(today);
  todayList.render('today');

  const tomorrowList = new CreateTodoList(tomorrow);
  tomorrowList.render('tomorrow');

  const afterTomorrowList = new CreateTodoList(afterTomorrow);
  afterTomorrowList.render('afterTomorrow');

  </script>
</body>

여기까지가 제가 구현했던 내용이고 이 코드를 작성한 후에 받은 피드백을 살펴보겠습니다

피드백

위 코드를 통해서 받은 피드백은 총 5가지였습니다

첫번째 피드백

    const ERROR_MESSAGE = {
      dataDefind: 'data is not defind',
      is_array: 'data type is not array',
      type_check: 'type error'
    }

리뷰어님: 에러 메세지를 분리시켜놓으셨군요. 👏
보통 이런 경우, key 값도 전부 대문자인 Snake Case를 쓰곤 합니다.

피드백을 받고 수정한 내용

    const ERROR_MESSAGE = {
      DATA_DEFIND: 'data is not defind',
      ARRAY_CHECK: 'data type is not array',
      TYPE_CHECK: 'type error'
    }

두번째 피드백

    let today = [
      {
        text: 'JS 공부하기',
        isComplete: true
      }
    ];

리뷰어님: data 들도 const를 사용해주시면 좋을 것 같아요

피드백을 받고 수정한 내용


    const today = [
      {
        text: 'JS 공부하기',
        isComplete: true
      }
    ];

세번째 피드백

  function validate(data) {
    if(data === null || undefined) throw new Error(ERROR_MESSAGE.DATA_DEFIND);

리뷰어님: data가 undefined면 어떻게 될까요?

const data = undefined
if (data === null || undefined) {
   console.log('123')
} else {
   console.log('456')
}

피드백을 받고 수정한 내용

null과 undefined 둘 다 false 값이기 때문에 둘을 구분할 수 없다

  if(!data) throw new Error(ERROR_MESSAGE.DATA_DEFIND);

논리 부정 연산자로 처리했다.

네번째 피드백

    data.map((item) => {
      if(typeof item.text !== 'string' || typeof item.isComplete !== "boolean") throw new Error(ERROR_MESSAGE.typeCheck)
    })

리뷰어님: 이런 경우는 forEach가 더 적절합니다. map과 forEach의 차이에 대해서 찾아보시는 걸 권장드려요

피드백을 받고 수정한 내용

    data.forEach((item) => {
      if(typeof item.text !== 'string' || typeof item.isComplete !== "boolean") throw new Error(ERROR_MESSAGE.TYPE_CHECK)
    })

해당 로직은 data내의 아이템을 순회하면서 원하는 데이터 형식이 맞는지 검증하는 로직인데
리액트에서 컴포넌트를 반복하면서 map을 자주 쓰다보니 그냥 아무 생각없이 map함수를 사용했습니다

forEach와 map의 차이

forEach 메서드는 자신의 내부에서 반복문을 실행한다.
forEach 메서드는 내부에서 반복문을 통해 자신을 호출한 배열을 순회하면서 수행해야할 처리를
콜백 함수로 전달받아 반복 호출한다.

map 메서드는 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출한다.(여기까지는 forEach와 동일) 
그리고 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다!(forEach와 가장 큰 차이점)

다섯번째 피드백

    this.render = function(id) {
      const todoList = document.querySelector(`#${id}`);
      todoList.innerHTML = this.data.map((item) => item.isComplete ? `<strike>${item.text}</strike>`: `${item.text}`).join('');
    };

리뷰어님: 이런 구조면 인스턴스 todayList, tomorrowList 간 간섭이 가능해지는데요.
인스턴스를 독립적인 개체로 보면 좋을 것 같아요.
id를 Todo 함수의 파라미터로 받으면 생성하는 시점에 인스턴스에서 사용할 DOM을 정해놓고,
다른 인스턴스에서 생성할 때를 제외하고 실수로라도 덮어쓸일이 없겠죠.

const todayList = new Todo(today);
todayList.render('today');

const tomorrowList = new Todo(tomorrow);
tomorrowList.render('today');

피드백을 받고 수정한 내용

  function CreateTodoList(data, element) {
    this.data = validate(data);
    this.element = element;
    this.render = function() {
      const todoList = document.querySelector(`#${element}`);
      todoList.innerHTML = this.data.map((item) => item.isComplete ? `<strike>${item.text}</strike>`: `${item.text}`).join('');
    };
  }

  const todayList = new CreateTodoList(today, 'today');
  todayList.render();

  const tomorrowList = new CreateTodoList(tomorrow, 'tomorrow');
  tomorrowList.render();

  const afterTomorrowList = new CreateTodoList(afterTomorrow, 'afterTomorrow');
  afterTomorrowList.render();

생성자 함수를 생성하는 시점에 data와 엘리먼트 id를 전달해주었습니다

기존에 작성한 방식에서 인스턴스간 간섭이 어떻게 가능한건지 잘 이해가 가지 않아서 다시 질문을 드렸습니다

질문: 생성자 함수에 인자로 element id 를 전달해주는 방법으로 바꿨는데 기존 방법으로 했을때 인스턴스간 간섭이 어떤식으로 가능하게 되는건지 예시를 들어주실 수 있을까요?


리뷰어님: 풀어말하면 render를 호출할때마다 인스턴스의 root DOM element가 render 함수의 파라미터 id에 의해 바뀔수 있다 에요.
지금은 간단한 미션이라 모두 예측이 가능한데, 컴포넌트 구조가 복잡했을때 실수할 확률이 높아지죠.
보너스 미션의 setState를 붙인다면, 이때도 id를 계속 넘겨줘야하는 구조라 에러가 발생할 확률이 높아집니다.

회고

이렇게 한주의 미션이 끝이났습니다.

첫주차 미션을 끝내고 나니 많은 생각이 들었습니다

간단한 todo list 구현이라고 생각했는데 코드를 작성하면서 생각보다 내가 놓치고 있는 게 많다는 생각이 들었고

개발하면서 뭔가를 만들어내고 기능을 동작시키는것도 중요하지만

정말 중요한건 단순히 만들고 동작시키는게 아닌 어떻게 하면 조금 더 효율적으로?

올바른 방법으로 구현하느냐 인 것 같습니다.

코드 리뷰 스터디는 총 4주간 진행되는데 앞으로 남은 주차도 열심히 해보겠습니다!

끄읏!

profile
꾸준함을 잃지 말자는 모토를 가지고 개발하고 있습니다 :)

0개의 댓글