Frontend/JavaScript
[바닐라 자바스크립트로 SPA 만들기] 2. 비동기 처리
반응형
지난 글에서는 카운터 컴포넌트를 통해 기본적인 SPA의 구조를 잡았다. 이번에는 조금 더 나아가서, API 통신을 위한 비동기 처리 컴포넌트를 만들어 보자.
비동기 컴포넌트 만들기
이 부분의 구조는 "프레임워크 없는 프론트엔드 개발" 도서의 HTTP요청 부분을 참고했으며, 더미 API를 통해 패치 컴포넌트를 만들어 볼 것이다.
폴더 구조
새롭게 추가된 부분은 다음과 같다.
- api/http.js
: HTTP 통신 메서드 모음
- components/List.js
: API 결과를 출력할 리스트 컴포넌트
- pages/FetchPage.js
: API 패치를 위한 페이지 컴포넌트
src/api/http.js
- Fetch API를 기반으로 HTTP 통신 메서드를 추상화한 파일이다. (참고 링크)
- 최대한 외부 라이브러리를 활용하지 않는 버전으로 만들기 위해 Fetch를 사용했지만, axios로 구현할 수도 있다.
const parseResponse = async (response) => {
const { status } = response;
let data;
if (status !== 204) {
data = await response.json();
}
return {
status,
data,
};
};
const request = async (params) => {
const { method = 'GET', url, headers = {}, body } = params;
const config = {
method,
headers: new window.Headers(headers),
};
if (body) {
config.body = JSON.stringify(body);
}
const response = await window.fetch(url, config);
return parseResponse(response);
};
const get = async (url, headers) => {
const response = await request({
url,
headers,
method: 'GET',
});
return response.data;
};
const post = async (url, body, headers) => {
const response = await request({
url,
headers,
method: 'POST',
body,
});
return response.data;
};
const put = async (url, body, headers) => {
const response = await request({
url,
headers,
method: 'PUT',
body,
});
return response.data;
};
const patch = async (url, body, headers) => {
const response = await request({
url,
headers,
method: 'PATCH',
body,
});
return response.data;
};
const deleteRequest = async (url, headers) => {
const response = await request({
url,
headers,
method: 'DELETE',
});
return response.data;
};
export default {
get,
post,
put,
patch,
delete: deleteRequest,
};
src/components/List.js
- API 응답으로 받은 결과값을 리스트로 출력하기 위해 생성한 컴포넌트이다.
- List 컴포넌트를 호출할 때 결과를 넘기면, 이 컴포넌트에서 props로 받아 활용할 수 있다.
- 참고로 map을 통해 리스트를 출력할 때,
join('')
으로 묶어주어야 배열의 쉼표를 없애고 출력할 수 있다.
import Component from '../core/Component.js';
export default class List extends Component {
template() {
const { dummyList } = this.$props;
return `
<ul>
${dummyList
.map(({ id, title }) => `<li key=${id}>${title}</li>`)
.join('')}
</ul>
`;
}
}
src/pages/FetchPage.js
jsonplaceholder
의 포스트 API를 활용했다.
1. 상태 및 템플릿 설정
- 응답 결과를 저장할 dummyList를 생성한다.
- List 컴포넌트를 호출할 태그를 생성한다.
setup() {
this.$state = {
dummyList: [],
};
}
template() {
return `
<h1>Fetch Page</h1>
<div data-component="fetch-api"></div>
`;
}
2. API 호출
- 컴포넌트가 마운트 되었을 때 API를 호출해야 한다.
- mounted 메서드에서 API호출 함수를 생성하고, dummyList에 값을 넣는다.
mounted() {
const fetchDummy = async () => {
const dummyPosts = await http.get(
`https://jsonplaceholder.typicode.com/posts`
);
this.setState({ dummyList: [...dummyPosts] });
};
fetchDummy();
}
3. 리스트 출력
- 리스트 컴포넌트에 dummyList를 넘겨야 한다.
- fetchDummy 호출 -> 리스트 컴포넌트 호출 순으로 동작해야 한다는 것이다.
- 그리고 dummyList에 값이 없을 때만(초기에만) fetchDummy를 호출해야 한다.
근데 만약 이렇게 작성할 경우 fetchDummy가 계속 호출되기 때문에 무한 루프에 빠지게 된다.
mounted() {
const fetchDummy = async () => {
const dummyPosts = await http.get(
`https://jsonplaceholder.typicode.com/posts`
);
this.setState({ dummyList: [...dummyPosts] });
};
fetchDummy();
const $fetchApi = this.$target.querySelector(
'[data-component="fetch-api"]'
);
new List($fetchApi, this.$state);
}
따라서 dummyList의 값을 확인하는 분기를 통해 API 요청과 리스트 컴포넌트 호출 부분을 나누어야 한다.
mounted() {
const fetchDummy = async () => {
const dummyPosts = await http.get(
`https://jsonplaceholder.typicode.com/posts`
);
this.setState({ dummyList: [...dummyPosts] });
};
if (this.$state.dummyList.length === 0) {
fetchDummy();
} else {
const $fetchApi = this.$target.querySelector(
'[data-component="fetch-api"]'
);
new List($fetchApi, this.$state);
}
}
최종 코드
import Component from '../core/Component.js';
import List from '../components/List.js';
import http from '../api/http.js';
export default class FetchPage extends Component {
setup() {
this.$state = {
dummyList: [],
};
}
template() {
return `
<h1>Fetch Page</h1>
<div data-component="fetch-api"></div>
`;
}
mounted() {
const fetchDummy = async () => {
const dummyPosts = await http.get(
`https://jsonplaceholder.typicode.com/posts`
);
this.setState({ dummyList: [...dummyPosts] });
};
if (this.$state.dummyList.length === 0) {
fetchDummy();
} else {
const $fetchApi = this.$target.querySelector(
'[data-component="fetch-api"]'
);
new List($fetchApi, this.$state);
}
}
}
렌더링 결과
Reference
- https://github.com/Apress/frameworkless-front-end-development/tree/master/Chapter05
- https://jsonplaceholder.typicode.com/posts
반응형
'Frontend > JavaScript' 카테고리의 다른 글
[바닐라 자바스크립트로 SPA 만들기] 3. 라우팅 (2) | 2023.06.26 |
---|---|
[바닐라 자바스크립트로 SPA 만들기] 1. 컴포넌트 만들기 (0) | 2023.06.24 |
[JavaScript] 코어 자바스크립트 7장 - 클래스 (0) | 2023.06.08 |
[JavaScript] 코어 자바스크립트 6장 - 프로토타입 (0) | 2023.06.07 |
[JavaScript] 코어 자바스크립트 5장 - 클로저 (0) | 2023.06.05 |