📖 오늘 배운 것
자바스크립트의 모듈화
- 자바스크립트에서 비동기 다루기
- 비동기 다루기 실습 (w. To do List)
- Promise
- async, await
💪 자바스크립트의 module에 대해 알아보자
- 솔직히 최근에는 Webpack이나 번들러로 많이 대체해서 쓰지 않을 수도 있다.
- 그래도 이번 기회에 잘 써서 익숙해지자!
Import
// export 키워드로 내보내진 변수, 함수 등등을 불러올 수 있는 키워드
// export default로 내보내진 경우
import a from 'folder';
// export 로만 내보내진 경우
import * as a from 'folder';
import {a, b} from 'folder';
// 이름이 너무 길거나 같을 경우
import {a as b} from 'folder';
// 우리가 어제 진행했던 Simple TodoList도 모듈화 할 수 있다.
// html에 script코드가 지저분하게 있었던 것을 모듈화하여
// before
<script src = 'src/a.js'></script>
<script src = 'src/b.js'></script >
<script src = 'src/c.js'></script>
//after
<script src = 'src/a.js' type="module"></script>
// 이렇게 바꿔쓰면 훨씬 깔끔하게 쓸 수 있다 (안에서 import export 하는 과정이 필요하다.)
모듈을 쓰지 않는 경우에는 script 태그의 순서가 중요하고 사용하지 않는 스크립트를 추적하기 힘들다.
모듈을 쓰게 되면 하나의 script태그 내에서 관리하니 순서 중요 X, 스크립트 추적 용이, 전역 오염 X
비동기 다루기
💡 비동기 처리란?
특정 연산이 끝날 때 까지 코드의 실행을 멈추지 않고 다음코드를 먼저 실행하는 자바스크립트의 특성
**비동기 함수의 종류**
1. addEventListener 함수
- 두번째 인자로 넘겨진 함수는 바로 실행되지 않고, 이벤트 리스너가 정의한 이벤트가 발생할 때 실행됩니다.
2. setTimeout과 setInterval
- 바로 실행되지 않고, 시간이 지난 후에 실행된다.
- setTimeout에서 0을 넣거나 지정하지 않아도 바로 실행되지 않는다.
3. XMLHttpRequest(XHR)
- 데이터를 비동기로 요청하고, 요청 후의 동작을 비동기로 처리한다.
비동기 다루기로 TodoList 만들기
이번에도 컴포넌트 방식으로 해볼 것이다.
하지만 api 요청이 필요하니 생각을 먼저 하고 시작하자.
그림을 그려봤다. 너무 못그렸다. 발전하겠다 💪
![[Pasted image 20221101235839.png]]
일단 크게 두개의 컴포넌트로 나눈다.
- TodoList 컴포넌트
- TodoComment 컴포넌트
TodoList 컴포넌트의 List를 클릭하면 해당 리스트에 대한 댓글이 TodoComments에 표시된다.
여기서 우리가 고민할 것은 의존성이다.
어떻게 하면 두 컴포넌트의 의존성을 없애며 존재하게 만들 수 있을까?
방법은 바로 상위 컴포넌트를 하나 더 두어 관리하는 것이다.
이렇게 하면 두 컴포넌트가 의존성을 갖지 않고 구현할 수 있다.
전체 구성은 이렇게 된다.
![[Pasted image 20221102000206.png]]
main - App - TodoList, TodoComments 구성이다.
api 통신은 자주 사용하니 따로 api.js로 빼서 구현한다.
TodoList.js
- 직접적으로 TodoList를 렌더링 하는 곳이다.
- App.js에서 onClick을 받아와서 TodoComment와의 의존성을 줄인다.
- 해당 함수에서 List를 클릭하면 id를 내보내준다.
export default function TodoList({$target, initialState, onClick}) {
const $element = document.createElement('div');
$target.appendChild($element);
this.state = initialState;
this.setState = nextState => {
this.state = nextState;
this.render();
}
this.render = () => {
if(Array.isArray(this.state)) {
$element.innerHTML = `
<h1>Simple Todo</h1>
<ul>
${this.state.map(({id, text} =>
`<li data-id=${id}>${text}</li>`)).join('')}
</ul>
`
}
$element.querySelectorAll('li').forEach(($li) => {
$li.addEventListener('click', ({target}) => {
const {id} = target.dataset;
onClick(parseInt(id, 10));
})
}
}
this.render();
}
TodoComments.js
- TodoComments의 렌더링 코드가 있는 곳
- App.js 에게 클릭된 투두와 댓글들을 받아 화면에 보여준다.
export default function TodoComments({$target, initialState, onClick}) {
const $comments = document.createElement('div');
$target.appendChild($comments);
this.state = initialState;
this.setState = nextState => {
this.state = nextState;
this.render();
}
this.render = () => {
const {selectedTodo, Comments} = this.state;
if(!selectedTodo || !comments) {
$comments.innerHTML = '';
return;
}
$comments.innerHTML = `
<h1>${selectedTodo.text}의 댓글들 </h1>
${comments.length === 0 ? "댓글이 없습니다." : ""}
<ul>
${comments.map(({content}) => `<li>${content}</li>`).join('')}
</ul>
`
}
this.render();
}
main.js
- index.html에 직접적인 모듈로 담기는 js 파일이다.
- #app 을 querySelector로 담아와 App.js에 뿌려주는 역할을 한다.
import App from './App.js';
const $app = document.querySelector('#app');
new App({ $target: $app });
App.js
- main.js에서 받은 #app 에 담길 돔 요소에 데이터를 뿌려주는 역할을 한다.
- api통신을 이쪽에서 맡을 것 이다.
import { request } from './api.js';
import TodoList from './TodoList.js';
import TodoComments from './TodoComments.js';
const REQUEST_URL = '...';
export default function App({$app}) {
this.state = {
todos: [],
selectedTodo: null,
comments: [],
}
const todoList = new TodoList({
$target: $app,
initialState: this.state.todos,
onClick: async (id) => {
const selectedTodo = this.state.todos.find((todo) => todo.id === id);
this.setState({
...this.state,
selectedTodo,
});
try {
// 로딩 시작하는 코드
const data = await request(REQUEST_URL)
.then((data) => {
this.setState({
...this.state,
comments: data
})
});
}catch (e) {
// 에러 코드
}finally {
// 로딩을 숨겨주는 코드
}
}
});
const todoComments = new TodoComments({
$target: $app,
initialState: {
selectedTodo: this.state.selectedTodo,
comments: this.state.comments
}
});
this.setState = (nextState) => {
this.state = nextState;
todoList.setState(this.state.todos);
todoComments.setState({
selectedTodo: this.state.selectedTodo,
comments: this.state.comments
})
}
// todo 불러오기
this.init = async () => {
const data = await request(REQUEST_URL).then((data) => {
this.setState({
...this.state,
todos: data,
})
})
}
this.init();
}
api.js
- 직접적인 api 소통을 담당하는 곳이다.
- XMLHTTPRequest로 구현되어 있다.
export function request(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', (e) => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(xhr.statusText);
}
}
});
xhr.addEventListener('error', (e) => reject(xhr.statusText));
xhr.open('GET', url);
xhr.send();
});
}
이렇게 모든 컴포넌트 구현이 끝났다 ㅎㅎㅎ
이제 프로미스와 async/await만 끝내자
Promise
비동기 작업을 제어하기 위해 나온 개념으로, callback hell에서 어느정도 벗어날 수 있게 해줍니다.
프로미스로 정의된 작업끼리는 연결할 수 있으며, 이를 통해 depth가 크게 증가하지 않는 효과가 있습니다.
const promise = new Promise((resolve, reject) => {
// promise 내부에서 비동기가 성공적으로 종료 => resolve
// promise 내부에서 오류 상황 => reject
});
- 프로미스는 then을 이용해 비동기 작업 이후 실행할 작업을 지정한다.
- 프로미스 내에서 작업이 실패할 경우 catch로 잡을 수 있다.
- 안넣을 경우 에러 잡기 힘드니 catch 문은 넣는 것이 좋다.
- 성공 실패 여부와 상관 없이 호출해야 하는 코드가 있다면 finally에서 처리
- 기존의 callback 함수를 promise 형태로 만들 수 있다.
Promise 내장 함수들
- Promise.all(iterable): 여러 프로미스를 동시에 처리할 때 유용하다.
=> 병렬적으로 처리할 것들을 그냥 한번에 처리
const delay = (time) => new Promise((resolve) => setTimeout(resolve, time));
const promise1 = delay(1000);
const promise2 = delay(2000);
const promise3 = delay(3000);
Promise.all([promise1, promise2, promise3]).then(() => {
console.log('SUMGGGG');
});
- Promise.race(iterable)
=> 여러 프로미스중 하나라도 resolve나 reject하면 종료된다. - Promise.any(iterable)
=> 여러 프로미스 중 하나라도 resolve 되면 종료된다. - Promise.allSettled(iterable)
=> 여러 프로미스가 끝날때까지 기다린다. (fulfilled (성공), rejected (실패)로 표현) - Promise.reslove
=> 주어진 값으로 이행하는 Promise.then 객체를 만든다.
=> 주어진 값이 Promise인 경우 해당 Promise가 반환된다.
=> 리턴값이 프로미스가 아닌데 프로미스로 맞춰주고 싶은 경우 사용됨 - Promise.reject
=> 강제로 reject 시켜야하는 경우 (잘안쓰임)
async await
프로미스가 콜백 뎁스를 1단계로 주어지긴 하지만 여전히 불편한 것은 사실이다.
코드의 흐름과 실행순서가 일치하지 않아서 불편하다.
async,await을 이용하면 Promise를 동기처럼 보이게 만듬
- async 키워드 함수가 붙은 함수는 결과를 Promise로 반환한다.
'데브코스' 카테고리의 다른 글
[VanillaJS] TodoList에 Drag & Drop 이벤트 적용시키기 (feat. taskQueue) (0) | 2022.11.24 |
---|---|
VanillaJS를 통한 자바스크립트 정리 1 (0) | 2022.10.31 |
프로그래머스 데브코스 2주차 회고록 😝 (0) | 2022.10.31 |