변경을 위해 기존 모듈 삽입 방식과 함수형 컴포넌트로 재구성을 진행합니다.
프로젝트의 구성도는 다음과 같습니다.
main.js ← App.js ← api/api.js
← components(floder) ← SearchInput.js
← SearchResult.js
← ImageInfo.js
index.html
...
<head>
...
<link rel="stylesheet" href="src/style.css" />
<script type="module" src="src/main.js"></script>
<title>Cat Search</title>
</head>
<body>
<div id="App"></div>
</body>
...
main.js
import App from "./App.js";
new App(document.querySelector("#App"));
함수형 컴포넌트에 대한 구성 이해도가 낮다면 컴포넌트 구현을 통해 회고 진행합니다.
앞서 API 요청을 확인한 결과 데이터는 다음과 같은 형태로 반환됩니다. 고려하여 관련된 부분들은 수정 합니다.
responseData: {
data: [{item}, {item}, {item}, ..., {item}],
}
App.js
import SearchInput from "./components/SearchInput.js";
import SearchResult from "./components/SearchResult.js";
import ImageInfo from "./components/ImageInfo.js";
import { request } from "./api/api.js";
export default function App($app) {
this.state = {
visible: false,
image: null,
data: [],
};
const searchInput = new SearchInput({
$app,
onSearch: async (keyword) => {
const searchData = await request("search", keyword);
this.setState({
...this.state,
data: searchData.data,
});
},
});
const searchResult = new SearchResult({
$app,
initialState: [],
onClick: (image) => {
this.setState({
visible: true,
image,
});
},
});
const imageInfo = new ImageInfo({
$app,
initialState: {
visible: false,
image: null,
},
});
this.setState = (nextState) => {
this.state = nextState;
searchResult.setState(this.state.data);
imageInfo.setState({
image: this.state.image,
visible: this.state.visible,
});
};
}
SearchInput.js
export default function SearchInput({ $app, onSearch }) {
this.$target = document.createElement("input");
this.$target.className = "SearchInput";
this.$target.placeholder = "고양이를 검색해보세요.|";
$app.appendChild(this.$target);
this.onSearch = onSearch;
this.$target.addEventListener("keyup", (e) => {
if (e.keyCode === 13) {
this.onSearch(e.target.value);
}
});
}
추가 요구사항에 EventDelegation을 이용하여 클릭 이벤트를 수정
이 있습니다. closest를 이용해서 이벤트 위임을 통한 최적화를 진행해준다. 해당 내용은 EventDelegation에 기재되어 있으니 회고 진행해주길 바랍니다.
SearchResult.js
export default function SearchResult({ $app, initialState }) {
this.state = initialState;
this.$target = document.createElement("div");
this.$target.className = "SearchResult";
$app.appendChild(this.$target);
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
this.render = () => {
if (this.state.data) {
this.$target.innerHTML = this.state.data
.map(
(cat, index) => `
<div class="item" data-index="${index}">
<img src=${cat.url} alt=${cat.name} />
</div>
`
)
.join("");
}
};
this.onClick = onClick;
this.$target.addEventListener("click", (e) => {
const $searchItem = e.target.closest(".item");
const { index } = $searchItem.dataset;
this.onClick(this.state[index]);
});
this.render();
}
ImageInfo.js
export default function ImageInfo({ $app, initialState }) {
this.state = initialState;
this.$target = document.createElement("div");
this.$target.className = "ImageInfo";
$app.appendChild(this.$target);
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
this.render = () => {
if (this.state.image) {
const { name, url, temperament, origin } = this.state.image;
this.$target.innerHTML = `
<div class="content-wrapper">
<div class="title">
<span>${name}</span>
<div class="close">x</div>
</div>
<img src="${url}" alt="${name}"/>
<div class="description">
<div>성격: ${temperament}</div>
<div>태생: ${origin}</div>
</div>
</div>`;
}
this.$target.style.display = this.state.visible ? "block" : "none";
};
this.render();
}