프로그래머스 '2022 Dev-Matching: 웹 프론트엔드 개발자(상반기)'
기출 문제 해설을 참고하여 작성하였습니다.
- 인풋에 값을 입력했을 경우, api 호출
const cache = {};
// api 호출 로직을 분리하여 캐싱 및 에러처리가 되도록 처리
export const request = async (url, errorMessage = "요청에 실패했습니다.") => {
if (cache[url]) {
return cache[url];
}
const res = await fetch(url);
if (res.ok) {
const data = await res.json();
cache[url] = data;
return data;
}
throw new Error(errorMessage);
};
import { debounce } from "../utils/debounce.js";
export default function SearchInput({ target, initialState, onChange }) {
/* (...생략) */
// 방향키 위, 아래, 엔터는 예외 처리 후, onChange를 props로 App.js에서 로직 처리
this.element.addEventListener(
"keyup",
debounce(({ key, target: { value } }) => {
const ignoreKeys = ["ArrowUp", "ArrowDown", "Enter"];
if (ignoreKeys.includes(key)) return;
onChange(value);
}, 500)
);
}
export default function App({ target }) {
/* (...생략) */
const searchInput = new SearchInput({
target,
initialState: "",
onChange: async (keyword) => {
// 검색 키워드가 없을 경우 검색결과 값을 초기화하고,
// 값이 있을 경우 api 호출
if (!keyword) {
this.setState({
fetchedLanguages: [],
});
} else {
const languages = await fetchLanguages(keyword);
this.setState({
fetchedLanguages: languages,
keyword,
});
}
},
});
}
- 인풋에 포커스 되어있을 경우, 화살표로 검색된 값을 선택 할 수 있게 처리
export default function Suggestion({ target, initialState, onSelect }) {
/* (...생략) */
window.addEventListener("keyup", ({ key }) => {
const { selectedIndex } = this.state;
const lastIndex = this.state.items.length - 1;
const keys = ["ArrowUp", "ArrowDown", "Enter"];
let nextIndex = selectedIndex;
if (keys.includes(key)) {
switch (key) {
case "ArrowUp":
nextIndex = selectedIndex === 0 ? lastIndex : selectedIndex - 1;
break;
case "ArrowDown":
nextIndex = selectedIndex === lastIndex ? 0 : selectedIndex + 1;
break;
case "Enter":
onSelect(this.state.items[this.state.selectedIndex]);
break;
default:
}
this.setState({ ...this.state, selectedIndex: nextIndex });
}
});
window.addEventListener("click", (e) => {
const idx = e.target.dataset.index;
if (!idx) return;
this.setState({ ...this.state, selectedIndex: idx });
onSelect(this.state.items[idx]);
});
}
export default function App({ target }) {
/* (...생략) */
// 검색결과 컴포넌트
const suggestion = new Suggestion({
target,
initialState: {
items: [],
selectedIndex: 0,
keyword: "",
},
onSelect: (language) => {
this.setState({
selectedLanguages: [...this.state.selectedLanguages, language],
});
},
});
}
- 인풋의 입력값과 동일한 텍스트는 음영처리
export default function Suggestion({ target, initialState, onSelect }) {
/* (...생략) */
this.renderMatchItem = (keyword, item) => {
const matchedText = item.match(new RegExp(keyword, "gi"))[0];
return item.replace(
new RegExp(matchedText, "gi"),
`<span class="Suggestion__item--matched">${matchedText}</span>`
);
};
this.render = () => {
const { items = [], selectedIndex, keyword } = this.state;
if (items.length > 0) {
this.element.style.display = "block";
this.element.innerHTML = `
<ul>
${items
.map(
(item, idx) =>
`<li class="${
Number(idx) === selectedIndex
? "Suggestion__item--selected"
: ""
}" data-index="${idx}">${this.renderMatchItem(
keyword,
item
)}</li>`
)
.join("")}
</ul>
`;
} else {
this.element.style.display = "none";
this.element.innerHTML = "";
}
};
this.render();
}
참고 레포지토리 : https://github.com/rrrrrrrrrrrocky/vanilla-js-autocomplete