fetch
함수로 데이터 불러오기fetch
함수는 기본적으로 url을 파라미터로 받고 Promise 객체를 반환한다.
fetch('http://example.com/movies.json')
.then((response) => {
return response.json();
})
.then((myJson) => {
console.log(JSON.stringify(myJson));
})
여기서 중요한 부분은, 이때 반환되는 Promise 객체는 HTTP error 상태를 reject하지 않는다. 그래서 HTTP 요청 중 에러가 발생했더라도 Promise의 catch로 떨어지지 않는다.
➡️ 그래서 요청이 정말로 성공했는지 체크하려면 response의 ok를 체크해야 한다.
fetch('http://example.com/movies.json')
.then((response) => {
if(!response.ok) { // response.ok가 없다면 에러를 내보낸다.
throw new Error('http 오류');
}
return response.json();
})
.then((myJson) => {
console.log(JSON.stringify(myJson));
})
.catch((err) => { // catch문 추가
alert(err.message);
})
실제로 fetch 함수로 호출하는 부분을 컴포넌트 내에 코딩하지 않고, 별도의 유틸리티 함수로 분리하는 것이 좋다.
각 컴포넌트가 데이터를 어떤 방식으로 불러오는지는 해당 컴포넌트의 관심사가 아니기 때문이다. (관심사 분리)
const API_END_POINT = '...';
const request = (nodeId) => {
// nodeId 유무에 따라 root directory를 조회할지, 특정 directory를 조회할지 처리한다.
fetch(`${API_END_POINT}/${nodeId ? nodeId : ''}`)
.then((response) => {
if (!response.ok) {
throw new Error('서버의 상태가 이상합니다!');
}
return response.json();
})
.catch((err) => {
throw new Error(`무언가 잘못되었습니다! ${err.message}`);
})
}
async
, await
사용하기async
문법은 Promise 형태의 응답을 반환하는 함수를 동기식 문법으로 작성할 수 있도록 도와주는 문법이다.
위의 request 함수를 async
, await
로 바꿔보면...
const request = async (nodeId) => {
try {
const response = await fetch(`${API_END_POINT}/${nodeId ? nodeId : ''}`); // <-- await
if (!response.ok) {
throw new Error('서버의 상태가 이상합니다!');
}
return await response.json(); // <-- await
} catch(err) {
throw new Error(`무언가 잘못되었습니다! ${err.message}`);
}
}
이제 fetch 함수를 넣었으니 App 컴포넌트에서 초기 데이터를 불러오게 하고, 이를 Nodes 컴포넌트에 적용하도록 코드를 수정해보자.
function App($app) {
this.state = {
isRoot: false,
nodes: [],
depth: []
}
// 파라미터 코드 생략
const breadcrumb = new Breadcrumb({...});
const nodes = new Nodes({...});
// App 컴포넌트에도 setState 함수 정의하기
this.setState = (nextState) => {
this.state = nextState;
breadcrumb.setState(this.state.depth);
nodes.setState({
isRoot: this.state.isRoot,
nodes: this.state.nodes
})
}
const init = async () => {
try {
const rootNodes = await request();
this.setState({
...this.state,
isRoot: true,
nodes: rootNodes
});
} catch (err) {
// 에러 처리하기
}
}
init();
}
이렇게 하면 최초 앱 실행 시, root directory의 데이터를 불러오는 부분이 완성된다.
Nodes 컴포넌트의 onClick 이벤트 핸들러를 아래와 같이 구현한다.
// 이전 코드 생략
const nodes = new Nodes({
$app,
initialState: [],
onClick: async (node) => {
try {
if (node.type === 'DIRECTORY') {
const nextNodes = await request(node.id);
this.setState({
...this.state,
depth: [...this.state.depth, node],
nodes: nextNodes
});
} else if (node.type === 'FILE') {
// 이미지 보기 처리하기
} catch (err) {
// 에러 처리하기
}
}
}
})
문제지에 보면, 아래의 마크업을 이용해서 ImageView를 처리하라고 안내되어 있다.
<div class="ImageViewer">
<div class="content">
<img src="https://fe-dev-matching-2021-03-serverlessdeploymentbuck-t3kpj3way537.s3.ap-northeast-2.amazonaws.com/public/images/a2i.jpg">
</div>
</div>
이 부분도 아래와 같이 컴포넌트로 만들어보자.
// 상수 처리
const IMAGE_PATH_PREFIX = 'https://fe-dev-matching-2021-03-serverlessdeploymentbuck-t3kpj3way537.s3.ap-northeast-2.amazonaws.com/public'
function ImageView({$app, initialState}) {
// image url
this.state = initialState;
this.$target = document.createElement('div');
this.$target.className = 'Modal ImageView';
$app.appendChild(this.$target);
this.setState = (nextState) => {
this.state = nextState;
this.render();
}
this.render = () => {
this.$target.innerHTML = `<div class="content">${this.state ? `<img src="${IMAGE_PATH_PREFIX}${this.state}" />` : ''}</div>`;
this.$target.style.display = this.state ? 'block' : 'none';
}
this.render();
}
App 컴포넌트 내에서 ImageView를 생성하고, Nodes에서 File을 클릭 시 ImageView에 따라 image url을 넘기는 방식으로 처리하면 된다.
this.state = {
isRoot: true,
nodes: [],
depth: [],
selectedFilePath: null
}
const imageView = new ImageView({
$app,
initialState: this.state.selectedNodeImage
})
this.setState = (nextState) => {
this.state = nextState;
breadcrumb.setState(this.state.depth);
nodes.setState({
isRoot: this.state.isRoot,
nodes: this.state.nodes
});
imageView.setState(this.state.selectedFilePath)
}
// 이전 코드 생략
const nodes = new Nodes({
$app,
initialState: [],
onClick: async (node) => {
try {
if (node.type === 'DIRECTORY') {
// Directory 처리 코드 생략
} else if (node.type === 'FILE') {
this.setState({
...this.state,
selectedFilePath: node.filePath
});
} catch (err) {
// 에러 처리하기
}
}
}
})
response.ok 하는 이유를 명확히 알았습니다 바닐라 js 언제 봐도 생소한거 같습니다 ㅎㅎ