PRGRMS 자바스크립트 스터디 2주차 회고(feat. 2주차 MVP)

허상범·2022년 10월 20일
1

프로그래머스에서 진행하는 자바스크립트 스터디에 참여, 학습한 내용을 적은 글입니다.

참여 스터디: [17기] 프론트엔드 개발을 위한 자바스크립트(feat. VanillaJS), 2022.09 - 2022. 10 (4주)
https://school.programmers.co.kr/learn/courses/15244

스터디 전체 회고는 1편 스터디 회고에 작성했습니다.


2주차 미션

  • 2주차 미션은 1주차에서 만든 Todolist를 개선하는 것이다.
    • TodoItem 추가, 완료처리, 개별 삭제, 전체 삭제 기능 추가
    • TodoItem의 전체 개수, 완료 개수 표시
    • 로컬 스토리지 활용
    • 커스텀 이벤트 활용해보기 등

주요 미션 작성 내용

1. 디렉토리 구조

  • 역할 별로 컴포넌트 분리
    • Todolist/TodoCount : 전체 개수, 완료 개수 표시
    • Todolist/TodoInput : 입력창
    • Todolist/TodoList : 투두 아이템 목록
    • index.js 에서 컴포넌트를 불러와 App 작성 및 실행

2. index.js

  • index.js에 App 컴포넌트를 작성했다. 여기서 필요한 컴포넌트들을 import하고 CRUD 기능을 작성했다.
  • 컴포넌트들 끼리 의존성이 없게 만들기 위해 App에서 각 컴포넌트에 콜백함수로 특정 함수들을 전달해줬다.
  • 로컬스토리지를 사용하기 때문에 따로 내부에 데이터를 저장하지 않고 요청할 때마다 로컬스토리지를 읽어옴
  • state를 내부에서도 저장해 관리하면 로컬스토리지와 이원화되서 관리가 더 어려울 것이라고 생각했음

//...

function App() {

  //...

  this.getState = () => {
    const state = JSON.parse(localStorage.getItem(STORAGE_KEY));

    if (state === null) {
      return INITIAL_STATE;
    }

    this.validateState(state);
    return state;
  };

  this.setState = (nextState) => {
    this.validateState(nextState);

    localStorage.setItem(STORAGE_KEY, JSON.stringify(nextState));

    const state = this.getState();
    todoList.render(state);
    todoCount.render(state);
  };

  this.addTodo = (inputValue) => {
    this.setState([
      ...this.getState(),
      { id: generateId(), text: inputValue, isCompleted: false },
    ]);
  };

  this.deleteTodo = (itemId) => {
    const nextState = this.getState().filter(({ id }) => id !== itemId);
    this.setState(nextState);
  };

  this.updateTodo = (itemId) => {
    const nextState = this.getState().map(({ id, text, isCompleted }) => {
      if (id !== itemId) return { id, text, isCompleted };
      return { id, text, isCompleted: !isCompleted };
    });
    this.setState(nextState);
  };

  const todoInput = new TodoInput({
    handleAddButtonClick: (inputValue) => this.addTodo(inputValue),
    handleremoveAllButtonClick: () =>
      this.$element.dispatchEvent(new CustomEvent('removeAll')),
  });

  const todoList = new TodoList({
    handleCheckBoxClick: (itemId) => this.updateTodo(itemId),
    handleItemTextClick: (itemId) => this.updateTodo(itemId),
    handleDeleteButtonClick: (itemId) => this.deleteTodo(itemId),
  });

  const todoCount = new TodoCount();

  todoList.render(this.getState());
  todoCount.render(this.getState());
  todoInput.clear();
}

window.addEventListener('DOMContentLoaded', () => {
  new App();
});

주요 피드백 & 개선

1. 로컬스토리지 기능을 유틸로 분리, 에러처리 추가

  • JSON.parse 를 사용할 때 에러와 404 상태 코드를 전부 확인할 수 있게 try...catch 구문을 추가
  • try...catch 구문을 사용하면서 코드 양이 많아서 로컬스토리지 관련 코드를 유틸로 이동

수정 된 코드

// js/utils.js

//...
const getLocalStorage = (key) => {
    const state = JSON.parse(localStorage.getItem(key));
    return state;
};

const setLocalStorage = (key, value) => {
    const newValue = JSON.stringify(value);
    localStorage.setItem(key, newValue);
};
//...

2. 데이터를 처리하는 컴포넌트 분리 (+ setState 개선)

  • index.js의 코드 양이 많고 컴포넌트들을 불러와서 처리하는 목적이 더 잘 보일 수 있도록 CRUD 세부 로직들을 TodoStore.js로 분리
  • setState 함수에 작성된 렌더함수들을 특정 함수로 분리
  • setState 함수에 renderAfterSetState 라는 콜백함수를 받아와 state 변경 이후 동작을 처리할 수 있도록 함
  • 수정한 이후에 이전 코드와 비교해보니까 관심사에 집중해 컴포넌트를 더 분리하니 훨씬 눈에 코드가 잘 들어왔다.

수정 된 코드

// TodoStore.js
export default function TodoStore({ renderAfterSetState }) {
  //...
  this.getState = () => {
    const state = getLocalStorage(STORAGE_KEY) || INITIAL_STATE;
    this.validateState(state);

    return state;
  };

  this.setState = (nextState) => {
    this.validateState(nextState);
    setLocalStorage(STORAGE_KEY, nextState);

    renderAfterSetState(nextState);
  };

  this.addTodo = (inputValue) => {
    this.setState([
      ...this.getState(),
      { id: generateId(), text: inputValue, isCompleted: false },
    ]);
  };

  this.deleteTodo = (itemId) => {
    const nextState = this.getState().filter(({ id }) => id !== itemId);
    this.setState(nextState);
  };

  this.updateTodo = (itemId) => {
    const nextState = this.getState().map(({ id, text, isCompleted }) => {
      if (id !== itemId) return { id, text, isCompleted };
      return { id, text, isCompleted: !isCompleted };
    });
    this.setState(nextState);
  };
  //...
}
// index.js

//...
function App() {
  //...
  this.$element = $('.app');
  this.$element.addEventListener('removeAll', () =>
    todoStore.setState(INITIAL_STATE)
  );

  const todoStore = new TodoStore({
    renderAfterSetState: (state) => this.render(state),
  });

  const todoInput = new TodoInput({
    handleAddButtonClick: (inputValue) => todoStore.addTodo(inputValue),
    handleremoveAllButtonClick: () =>
      this.$element.dispatchEvent(new CustomEvent('removeAll')),
  });

  const todoList = new TodoList({
    handleCheckBoxClick: (itemId) => todoStore.updateTodo(itemId),
    handleItemTextClick: (itemId) => todoStore.updateTodo(itemId),
    handleDeleteButtonClick: (itemId) => todoStore.deleteTodo(itemId),
  });

  const todoCount = new TodoCount();

  this.render = (state) => {
    todoList.render(state);
    todoCount.render(state);
    todoInput.clear();
  };

  //...
}

스터디 소감

  • 자잘한 html 태그 실수 부터 유니크ID 고민, 컴포넌트 분리에 대한 고민들까지 다양한 피드백을 있어서 좋았다. 이번 실시간 세션에서는 신입 프론트엔드 취준에 대한 얘기를 많이했는데 어디에 집중을 더하는 게 좋을지 판단하는 데 도움이 되었다.
  • 이번 주차에서는 운 좋게 MVP를 받게 됐다. 생각지도 못했기에 기분이 좋았고 이때 받은 커피 쿠폰으로 스타벅스에서 샌드위치를 사먹은 기억이 난다.

0개의 댓글