비동기
- 특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 것
- 일단 작업 스케줄러에 등록하고, 메인 스레드는 다른 일을 함
- 싱글 스레드라서 필요하기도 함
[예시]
addEventListener
함수 ⇒ 두번째 인자 함수는 이벤트가 발생할 때가 되어서야 실행
setTimeout
,setInterval
⇒ 첫번째 인자 함수는 시간 만큼 지난 후에야 실행- 0으로 넣어도 바로 실행되지 않음, 끝까지 코드가 다 실행되고 나서 실행
XMLHttpRequest(XHR)
⇒ ‘데이터 요청 & 요청 후 동작’을 모두 비동기로
- 콜백 함수 안에 콜백 함수 안에 콜백 함수..
- 비동기 작업이 많아지면 depth가 깊어져 비동기 지옥이 일어날 수도..
- 그렇다고 동기로 작업하면 (depth 지옥 없이 가독성이 좋아지긴 하겠지만)
요청 후 응답이 오기 전까지 기다린다고 브라우저가 굳어버림!!!! (X)
[비동기로 투두 리스트&클릭 시 댓글 리스트 띄우는 작업해보기]
- api를 호출해서 데이터를 받아오자!
- api.js와 TodoList, TodoComments 컴포넌트 만들기~
- dummy data로 초기값을 넣어주어, api로 데이터를 받기 전에 컴포넌트 테스트를 먼저 해보는게 좋다.
- 리스트의 아이템을 클릭했을 때 custom attribute를 통해 구분한다
App.js
import { request } from './api.js'; import TodoList from './lib/todoList.js'; import TodoComments from './lib/todoComments.js'; export default function App({$target}) { this.state = { todos : [], selectedTodo : null, comments : [] }; this.setState = (newState) => { this.state = newState; todoList.setState(this.state.todos); commentList.setState({ selectedTodo: this.state.selectedTodo, comments: this.state.comments }); } const todoList = new TodoList({ $target, initialState: this.state.todos, onClick: (id) => { const selectedTodo = this.state.todos.find(todo => todo.id === id); this.setState = { ...this.state, selectedTodo } //댓글 목록 불러오기 request(`https://kdt-frontend.programmers.co.kr/comments?todo.id=${id}`, (comments) => { this.setState({ ...this.state, comments }) }); } }) const commentList = new TodoComments({ $target, initialState : { selectedTodo: this.state.selectedTodo, comments: this.state.comments } }) //todos 불러오기 this.init = () => { request("https://kdt-frontend.programmers.co.kr/todos", (todos) => { this.setState({ ...this.state, todos }) }) } this.init(); }
api.js
export function request(url, successCallback, failCallback) { const xhr = new XMLHttpRequest(); xhr.addEventListener("load", (e) => { if (xhr.readyState === 4) { if (xhr.status === 200) { successCallback(JSON.parse(xhr.responseText)); } else { failCallback(xhr.statusText); } } }) xhr.addEventListener('error', (e) => failCallback(xhr.statusText)) xhr.open('GET', url) xhr.send() }
TodoList.js
export default function TodoList({ $target, initialState, onClick }) { let isInit = false; const $element = document.createElement('div'); $target.appendChild($element); this.state = initialState; this.setState = (newState) => { this.state = newState; this.render(); } this.render = () => { if (Array.isArray(this.state)) { $element.innerHTML = ` <h1>Simple TodoList</h1> <ul> ${this.state.map(({id, text}) => `<li data-id=${id}>${text}</li>`).join('')} </ul> `; } if (!isInit) { $element.addEventListener('click',(event)=> { if (event.target.tagName === "LI") { const todoId = event.target.dataset.id; onClick(Number(todoId)); } }); isInit = true; } } this.render(); }
TodoComments.js
export default function TodoComments({ $target, initialState }) { const $comments = document.createElement('div'); $target.appendChild($comments); this.state = initialState; this.setState = (newState) => { this.state = newState; this.render(); } this.render = () => { const { selectedTodo, comments } = this.state; if (!selectedTodo || !comments) { $comments.innerHTML = '' return } $comments.innerHTML = ` <h2>${selectedTodo.text}의 댓글들</h2> ${comments.length ? '' : '댓글이 없습니다'} <ul> ${comments.map(({content}) => `<li>${content}</li>`).join('')} </ul> ` } this.render(); }
custom attribute :
data-*
속성- 어느 엘리멘트에나
data-
로 시작하는 속성을 추가하면 사용자 속성이 됨
- 대시 스타일~
- 사용자 인터페이스에 노출되지 않지만, 개발자 도구에서 확인 가능 (고로 id 같은 것은 쓰지 않는 것이 좋다)
- attribute의 값은 대문자 포함 x, 항상 문자열로 변환된다.
- 접근 : .dataset.속성
- 속성 : 대시 뒤에
- 대시들을 카멜케이스로 전환해야 함
- .dataset 은 해당 data-* 속성들의 값을 다 가져옴
- 지원하지 않는 브라우저에서는 getAttribute()를 이용해야 함
<article id="electriccars" data-columns="3" data-index-number="12314" data-parent="cars"> ... </article>
var article = document.getElementById("electriccars"); article.dataset.columns; // "3" article.dataset.indexNumber; // "12314" article.dataset.parent; // "cars"