티스토리 뷰

프로그래머스에서 'roto'님이 진행하는 '[스터디/9기] 프론트엔드 개발을 위한 자바스크립트(feat. VanillaJS)'를 수강하며 배운 내용을 정리한 글입니다.

 

4주차 미션

구현 기능

  1. Todo App API를 이용하여 TodoList 구현하기
    • 할 일 목록 불러오기
    • 할 일 추가하기
    • 할 일 완료여부 토글하기
    • 할 일 삭제하기
    • 특정 사용자의 할 일 전체 삭제하기
  2. Users 컴포넌트를 만들고 선택된 사용자의 할 일 보여주기
  3. API가 느린 경우의 인터렉션 처리하기
  4. 미니 트렐로 - 드래그 앤 드롭으로 할 일 완료여부 토글하기
  5. (추가) ErrorUI 컴포넌트 만들기

구현하며 신경쓴 부분

  1. 기존 미션 코드를 참고하지 않고 빈 파일에서 하나씩 구현하며 지난 미션 내용들 복습하기
  2. 2주차 세션시간에 배운 시멘틱한 HTML 구조 만들기
  3. 접근성을 고려하여 HTML 작성하기
  4. 규칙에 맞춰 commit message 작성하기

 

구현하기

1. Todo App API를 이용하여 TodoList 구현하기

TodoList 구현은 1, 2주차와 큰 차이가 없었다. 다만 다른 점은 할 일 조회, 추가, 삭제 등의 모든 기능을 Todo API를 통해 진행한다는 것이다.

// api.js

export const deleteTodo = async (userName, todoId) => {
  const response = await fetch(`${END_POINT}/${userName}/${todoId}`, {
    method: 'DELETE',
  })

  return response.ok
}

그 외에 컴포넌트를 구성하고 화면에 그리는 것은 같아 이전 코드를 가져와서 수정해도 되었다. 하지만 마지막 미션이다보니 복습하는 마음으로 처음부터 한땀한땀 구현해나갔다.

이번 미션을 진행하며 스스로에게 '접근성 높고 시멘틱한 HTML 구조 만들기'라는 보너스 미션(?)을 주어서 생각없이 <div>태그를 사용하지 않으려고 노력했다. 컨텐츠들은 <main>태그에 담고 화면에는 보이지 않아도 스크린 리더 사용자가 무엇을 위한 앱인지 알 수 있도록 <h1>요소도 추가했다.

// App.js

init = () => {
  const $h1 = createElementWithClass({ tagName: 'h1', className: 'sr-only' })
  $h1.textContent = 'Todolist'

  this.$main.insertAdjacentElement('afterbegin', $h1)
}
/* style.css */

.sr-only {
  position: fixed;
  top: -100px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

구현 결과

TodoList 구현 결과

 

2. Users 컴포넌트 만들기

이번 미션에서 사용한 Todo API는 할 일을 조회하거나 추가, 삭제할 때 특정 사용자의 이름을 받는다. Todo API를 이용하여 전체 사용자 목록을 가져와 화면에 그리고, 특정 사용자를 선택하면 해당 사용자의 할 일 목록을 보여주는 기능을 구현한다. 사용자 목록을 어떤 식으로 보여줄 지 고민하다 사이드바 형식으로 보여주기로 결정했다.

사이드바 형식이고 사용자목록은 리스트이며, 각 사용자는 사용자별 할 일 조회 버튼이니 아래와 같이 구성했다.

<aside>
  <h2>Users</h2>
  <ul>
    <li>
      <button>user1</button>
    </li>
    <li>
      <button>user2</button>
    </li>
    <li>
      <button>user3</button>
    </li>
  </ul>
</aside>

구현 결과

Users 컴포넌트 구현 결과

사용자 목록을 구현하고 스타일도 설정했다. 사실 CSS는 이 스터디에서 커버하는 영역이 아니라 해도 그만, 안해도 그만이다. 하지만 미션을 진행하는 내내 기본 UI로 된 결과물을 보고 있는게 더 괴로워서 보기에 깔끔한 정도로만 스타일을 설정해주었다. 이후에 미니트렐로를 추가할 것을 고려하여 TodoList 가로가 영역의 반을 차지하도록 만들었다. (PR올리고 다른 스터디원이 CSS 칭찬해주셔서 뿌듯😊)

 

3. API가 느린 경우 Loading 인터렉션 처리하기

사용한 Todo API는 할 일 목록을 불러올 때, delay시간을 설정할 수 있었다. delay시간을 설정하고 API요청이 처리되는 동안 사용자에게 Loading중임을 보여주는 UI처리를 해야한다. TodoList 컴포넌트에 로딩중인지 여부를 알려주는 isLoading을 상태로 추가하고 isLoadingtrue인 경우 제목에 'Loading...'이라는 글자를 띄워 로딩중임을 표시했다.

// api.js

const DELAY_MS = 2000

export const fetchTodos = async (userName) => {
  const response = await fetch(`${END_POINT}/${userName}?delay=${DELAY_MS}`)

  return await response.json()
}
// App.js

updateTodos = async () => {
  this.setState({ nextIsLoading: true })
  
  const nextTodos = await fetchTodos(this.userName)
  
  this.setState({ nextIsLoading: false, nextTodos })
}

구현 결과

Loading 화면 구현 결과

 

4. 미니 트렐로

미니 트렐로를 구현하기 위해 Trello라는 컴포넌트를 만들고 TodoList 컴포넌트를 Trello의 하위 컴포넌트로 두었다. TodoList 컴포넌트에 status라는 상태를 추가해서 해당 컴포넌트가 진행중인 할 일에 대한 컴포넌트 인지 완료된 할 일에 대한 컴포넌트인지 알려주고, 진행 상태에 맞춰 할 일 데이터의 완료 여부를 필터링하여 TodoList 컴포넌트에 넘겨주었다.

이후 드래그 앤 드롭으로 할 일 상태를 토글하는 기능을 구현했다. 다행이 MDN 공식문서(HTML 드래그 앤 드롭 API)에 예시코드가 잘 되어있어서 참고해서 구현했다. 한 번에 잘 작동해서 감격하던 와중에 진행중인 할 일에서 진행중인 할 일로 드래그 앤 드롭해도 할 일 상태가 토글되는 것을 발견했다. 역시 한 번에 될리가 없지... 할 일 상태를 토글하기 전에 출발지점과 도착지점을 비교하여 다른 경우에만 상태를 토글하도록 수정해주었다. 결과적으로 드래그 이벤트는 양쪽에서 모두 동일하게 일어나므로 Trello 컴포넌트에서 설정하고 드롭 이벤트는 드롭 지점에 따라 상태를 토글할 지 말지 여부를 구분해야 해서 TodoList 컴포넌트에서 설정해주었다.

// TodoList.js
// 1. 각 할 일 목록에 draggable="true" 설정하기

 `<li id="${todo._id}" draggable="true">
   <label>
     <input type="checkbox" ${todo.isCompleted ? 'checked' : ''}/>
     <span>${todo.content}</span>
   </label>
   <button title="할 일 삭제">삭제</button>
 </li>`
// Trello.js
// 2. 드래그 시작 이벤트 설정

this.dragstartHandler = (e) => {
    e.dataTransfer.setData('text/plain', e.target.id)
  }

this.$Trello.addEventListener('dragstart', this.dragstartHandler)
// TodoList.js
// 3. dragover 이벤트 설정

this.dragoverHandler = (e) => {
  e.preventDefault()
  e.dataTransfer.dropEffect = 'move'
}

this.$TodoList.addEventListener('dragover', this.dragoverHandler)
// TodoList.js
// 4. drop 이벤트 설정

this.dropHandler = (e) => {
  e.preventDefault()

  const draggedEleId = e.dataTransfer.getData('text/plain')  // 드래그 앤 드롭한 li
  const draggedEleParent = document.getElementById(draggedEleId).parentElement  // ul
 
  // 도착한 ul이 드래그 앤 드롭한 li의 부모인 ul과 다른 경우
  if (this.$list !== draggedEleParent) {
    this.onToggleTodo(draggedEleId)
  }
}

this.$TodoList.addEventListener('drop', this.dropHandler)

구현 결과

미니 트렐로 구현 결과

 

5. ErrorUI 컴포넌트 만들기

원래는 위의 예시 코드에는 없지만 API요청의 결과(response.ok)를 검사해서 실패했다면 에러를 던지도록 구현했다. 하지만 API 요청이 실패하는 경우 에러를 던지지 말고 response status에 따른 Error UI 처리를 해주면 좋겠다는 리뷰를 받았다. 

스터디 진행중에는 Error UI를 만들지 못하고 끝났는데, 이후 그 리뷰가 계속 생각나서 시간을 내 빠르게 추가해보았다. 처음에는 Modal형식으로 Error UI를 보여주려고 했다. 하지만 사용성을 생각하면 팝업을 띄워 닫기 버튼을 클릭하게 만드는 것 보다 화면 어딘가에서 오류에 대해 알려주고 확인만 할 수 있도록하는 것이 좋겠다는 생각이 들었다. 그래서 오류가 발생하면 상단에 오류 메세지를 보여주고, 이후 어떤 요청이 정상적으로 처리되면 자동으로 사라지도록 만들었다.

// App.js

updateTodos = async () => {
  this.setState({ nextIsLoading: true })
  const nextTodos = await fetchTodos(this.userName)

  nextTodos === 'error'
    ? this.setState({
        errorMessage: 'Error : 할 일 목록 가져오기에 실패했어요ㅠㅠ',
        nextIsLoading: false,
        nextTodos: [],
      })
    : this.setState({ nextIsLoading: false, nextTodos })
}

setState = ({ errorMessage, nextTodos, nextIsLoading }) => {
  // 새로운 상태에 errorMessage가 없으면 errorUI를 화면에서 지운다
  errorMessage
    ? this.components.errorUI.setState({
      nextIsShowComp: true,
      nextErrorMessage: errorMessage,
    })
  : this.components.errorUI.setState({ nextIsShowComp: false })
	
  // ...
}

구현 결과

ErrorUI 구현 결과

 

미션을 진행하며

웹 접근성

웹 접근성에 대해 검색하다 실제 스크린리더를 사용하시는 분이 각 태그별로 어떻게 읽히는지 정리해둔 글을 발견했다. 오래된 글이긴 했지만 웹 접근성의 중요성을 알기에 충분한 글이었다. 전에 듣다 만 부스트코스 웹 UI 개발 강의에도 웹 접근성에대한 이야기가 있었다. 생각난 김에 마저 들어봐야겠다.

Commit Message

이번 미션을 진행하며 commit message 작성 규칙을 찾아보고 적용해보려고 노력했다. 이전까진 한글로 작성했지만 이번에는 영어로 작성해 보았는데, 짧은 문장인데도 영어로 작성하려니 원하는 바를 제대로 표현 못한 느낌이 들었다. 영어공부 절실하다...

commit message




참고 사이트




댓글