[DAY 33] VanillaJS를 통한 자바스크립트 기본역량 강화 Ⅱ (5)

송히·2023년 11월 6일
post-thumbnail

Today I Learn📖

  • To do list Drag & Drop (강의)

To do list Drag & Drop

  • <태그명 draggable="true">: 드래그가 가능한 요소로 만들어주는 속성

기본적인 드래그 & 드랍 구현 (최적화 X)

  1. 드래그로 가져올 요소(li)에 draggable="true" 설정
  2. 이벤트리스너로 dragstart, dragover, drop 구현
    -> dragover, drop에 e.preventDefault() 꼭 쓰기
    => 브라우저의 기본 드래그&드랍(콘텐츠를 자동으로 다른 위치 / 애플리케이션으로 이동시키기) 대신 우리가 만들 TodoList의 드래그&드랍을 사용하기 위해서 !
  3. drop된 콘텐츠가 본인 그룹의 것이 아니면 onDrop으로 상위 컴포넌트로 알림
  4. App.js에서 해당 콘텐츠의 완료 여부 상태를 토글해서 변경 후 fetchTodos로 화면 그리기
// App.js
import { request } from "./api.js";
import TodoList from "./TodoList.js";

export default function App({ $target }) {
  this.state = {
    todos: []
  }
  
  const incompletedTodoList = new TodoList({
	$target,
	initialState: {
	  title: '완료되지 않은 일들',
	  todos:[]
    },
    onDrop: async (todoId) => { // 드랍됐을 때 완료 여부 상태를 토글로 바꿈
	  await request(`/${todoId}/toggle`, {
      method: 'PUT' // 클릭 형식의 toggle과 동작 원리 같은 것 !
      })
      await fetchTodos()
    }
  });

  const completedTodoList = new TodoList({
	$target,
	initialState: {
	  title: '완료된 일들',
	  todos:[]
    },
    onDrop: async (todoId) => {
	  await request(`/${todoId}/toggle`, {
      method: 'PUT' // 사실 클릭 형식의 toggle과 동작 원리 같음
      })
      await fetchTodos()
    }
  });
  
  this.setState = nextState => {
	this.state = nextState;

    const { todos } = this.state
    
    incompletedTodoList.setState({
	  ...incompletedTodoList.state,
	  todos: todos.filter (todo => !todo.isCompleted)

    })
    completedTodoList.setState({
	  ...completedTodoList.state,
	  todos: todos.filter (todo => todo.isCompleted)
    })
  }
  
  const fetchTodos = async () => {
    const todos = await request('')

    this.setState({
      ...this.state, 
      todos
    })
  }

  fetchTodos()
}


// TodoList.js
export default function TodoList({ $target, initialState, onDrop }) {
  const $todoList = document.createElement('div')
  $todoList.setAttribute('droppable', 'true')
  $target.appendChild($todoList)

  this.state = initialState

  this.setState = nextState => {
	this.state = nextState
	this.render()
  }

  this.render = () => {
    const { title, todos = [] } = this.state
    $todoList.innerHTML = `
      <h2>${title}</h2>
      <ul>
		/* draggable 속성으로 드래그 가능하게 만들기 */
        ${todos.map(todo => `<li draggable="true">${todo.content}</li>`).join('')}
      </ul>
      ${todos.length === 0 ? '설정된 일이 없습니다.' : ''}
    `
  }

  this.render()
  
  /* 드래그 된 요소의 id값 가져오기 */
  $todoList.addEventListener('dragstart', e => {
	const $li = e.target.closest('li')
	e.dataTransfer.setData('todoId', $li.dataset.id)
  })
  
  /* 드래그 되는 방식 (move, copy, none, link) */
  $todoList.addEventListener('dragover', e => {
	e.preventDefault() // 필수
	e.dataTransfer.dropEffect = 'move'
})
  
  /* drop 시키기 */
  $todoList.addEventListener('drop', e => {
	e.preventDefault() // 필수

	const droppedTodoId = e.dataTransfer.getData('todoId')
    
    /* drop된 게 현재 TodoList에 없을 경우, 상위 컴포넌트에 알림 */
	const { todos } = this.state

    if (!todos.find(todo => todo.id === droppedTodoId)) {
      onDrop(droppedTodoId)
    }
  })
}

기본적인 drag&drop 디벨롭 시키는 방법

  • UI는 낙관적 업데이트로 사용자 경험 향상, 데이터, 네트워크 처리TaskQueue에 쌓아둔 뒤 한꺼번에 처리
    -> 한꺼번에 저장하는 버튼 만들어서 사용해도 됨 (그 외에도 여러 방법 존재)

    // TaskManager.js
    export default function TaskManager() {
      const tasks = []
      this.addTask = (task) = {
        tasks.push(task)
      }
    
      this.run = async () => {
        if (tasks.length > 0) {
          const task = tasks.shift()
          await task()
          this.run ()
        }
      }
    
    this.hasTask = () => tasks.length > 0
    }
    
    // App.js
    ...
    import TaskQueue from "./TaskManager.js"
    
    export default function App({ $target }) {
      const tasks = new TaskManager();
    
      ...
    
        tasks.addTask(async() => {
          await request ('/${todoId}/toggle', {
            method: 'PUT'
          })
        })
      }
    });
    
    ...
    
      const $button = document.createElement('button')
      $button.textContent = '변경내용 동기화'
      $target.appendChild($button)
    
      $button.addEventListener('click', () => tasks.run())
    }

웹 워커 (Web Workers)

메인 스레드 바깥인 백그라운드 스레드에서 스크립트를 실행하므로, 자바스크립트의 싱글 스레드를 보완 가능
-> 멀티 스레딩을 가능하게 하므로 UI 스레드가 블로킹되지 않아 사용자 경험 개선됨, 백그라운드에서 복잡한 작업 수행 가능

  • 메인 스레드와 별도의 실행 컨텍스트를 가지므로 DOM에 직접 접근 불가능
    -> 웹 페이지의 HTML 요소를 직접 조작하거나 글로벌 변수를 공유할 수 없음
  • postMessage, onmessage 이벤트를 통해 메인 스레드와 데이터를 주고받음
    -> 전송되는 데이터는 복사본이므로 메모리 공유는 X

  • 비슷한 코드들 (App.js의 incompletedTodoList, completedTodoList) 별도의 핸들러 함수로 빼서 처리하기

  • requestIdleCallback(): 브라우저가 더 이상 할 일이 없을 때 실행하는 함수 (모든 브라우저에서 지원하는 것은 아님)
  • requestanimationframe(): 화면의 그래픽이 부드럽게 움직이도록 새로고침 될 때마다 실행되는 함수

에이잭스 (AJAX, Asynchronous JavaScript and XML)

웹 페이지가 서버와 비동기적으로 데이터를 교환할 수 있게 해주는 기술(이자 현상)
-> 웹 페이지 전체를 새로고침하지 않고도 일부분만 업데이트 가능

  • 작동 방법
    1. fetch API 등으로 비동기 통신
    2. JSON으로 서버와 데이터 주고받음
    3. JavaScript & DOM으로 해당 부분 동적 업데이트
    4. 해당 부분에 CSS 존재할 경우, CSS 적용


😊오늘의 느낀점😊

드래그 & 드랍의 기본 틀을 만들어두고, 작업을 더 효율적으로 최적화시키며 개선하는 강의였다.
오늘 새로운 단어도 많이 들었고, TaskQueue를 통해 작업을 쌓아두었다가 한꺼번에 처리하는 방법도 배웠다. 여기서 사용되는 테스크 큐가 알고리즘때 배운 라는 게 신기했다 ㅎㅎ 알고리즘을 어떻게 적용하는 거지? 라는 생각을 했었는데 최적화 시키는데 사용됨을 알게됐다 !

데이터를 주고받기만 하지 않고, 데이터의 흐름과 네트워크 요청을 어떻게 잘 제어할 것인가? 를 고민해야함을 느꼈다. 여기서 효율성이 판가름 되니까..!!

오늘로 바닐라 JS 2까지 끝났다. 실무에서는 JS만으로 작업할 일이 거의 없을 것이라고 하시지만, 강사님과 라이브러리 없이 JS만으로 웹앱 만들기를 배우며 가지를 배울 수 있었다.
1. 다른 라이브러리 / 프레임워크의 동작 원리를 더 쉽게 이해할 수 있는 능력
2. 컴포넌트 단위로 구현하며 각 컴포넌트의 독립성을 챙기며 유지보수 용이성, 확장 가능성 등을 향상시킴
-> 특히 UI를 컴포넌트 단위로 추상화 해서 사용하기
3. 상태 & 데이터 중심의 UI 표현 -> 이를 통한 화면의 변화 추적

로토강사님 덕분에 많이 배웠습니다! 고생 많으셨습니다 🙇🏻‍♀️

profile
데브코스 프론트엔드 5기

0개의 댓글