개발 알다가도 모르겠네요

프로그래밍 언어 검색 -1 본문

웹/프론트엔드 직군 과제

프로그래밍 언어 검색 -1

이재빵 2022. 8. 26. 19:32
728x90

컴포넌트 구조

 

App 컴포넌트가 세 컴포넌트를 제어하는 형태이다.

SelectedLanguages, SearchInput, Suggestion 각각의 컴포넌트는 서로 의존성을 띄지 않는 형태로 작성해야 재사용이 가능하다.

 

 

App 컴포넌트 작성

// App.js
export default function App({ $target }) {
  this.state = {
    fetchedLanguages: [],
    selectedLanguages: [],
  };
  this.setState = (nextState) => {
    //TODO: 구현해야함
  };
}

그 후, App 컴포넌트를 생성하는 index.js를 선언, index.html에서 불러오게 한다.

 

// index.js
import App from "./App.js";

new App({ $target: document.querySelector(".App") });
// index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>검색기능</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <main class="App">
      <div class="SelectedLanguage"></div>
    </main>
    <script defer src="./index.js" type="module"></script>
  </body>
</html>

 

SearchInpiut 컴포넌트 구현

기초 구현

나머지 컴포넌트들을 구현, App에서 이를 제어하는 형태로 만든다.

//SearchInput.js
export default function SearchInput({ $target, initialState }) {
  this.$element = document.createElement("form");
  this.$element.className = "SearchInput";
  this.state = initialState;

  $target.appendChild(this.$element);

  this.render = () => {
    this.$element.innerHTML = `<input class="SearchInput__input" type="text" placeholder="프로그래밍 언어를 입력하세요." value="${this.state}">`;
  };

  this.render();
}

 기초적으로 구현한 SearchInput 컴포넌트를 App 컴포넌트를 이용해 렌더링한다.

import SearchInput from "./SearchInput.js";

export default function App({ $target }) {
  this.state = {
    fetchedLanguages: [],
    selectedLanguages: [],
  };
  this.setState = (nextState) => {
    //TODO: 구현해야함
  };

//추가된 내용
  const searchInput = new SearchInput({
    $target,
    initialState: "",
  });
}

 

출력 결과


API 연동하기

이제 키 입력 이벤트를 이용해 API를 호출해보록 하자.

 

//api.js
export const API_END_POINT = "API END POINT";

const request = async (url) => {
  const res = await fetch(url);

  if (res.ok) {
    const json = await res.json();
    return json;
  }

  throw new Error("요청에 실패함");
};

export const fectchLanguages = async (keyboard) =>
  request(`${API_END_POINT}/languages?keyword=${keyword}`);

재사용성을 고려하여 fetch를 호출하는 request함수를 별도로 정의하고, 이 함수를 이용해 언어 목록을 조회하는 

fetchLanguages 함수를 생성한다.

fetch 사용시, response의 ok를 꼭 검사해야 올바른 호출이 됐는지 체크가능하다.
https://developer.mozilla.org/ko/docs/Web/API/Fetch_API/Using_Fetch
https://developer.mozilla.org/en-US/docs/Web/API/Response/ok

 

이제 SearchInput 컴포넌트의 생성자 parameter로 onChange를 추가하고, 입력 이벤트 발생시 해당 이벤트를 발생시키게 한다.

//SearchInput.js
export default function SearchInput({ $target, initialState, onChange }) {
  this.$element = document.createElement("form");
  this.$element.className = "SearchInput";
  this.state = initialState;

  $target.appendChild(this.$element);

  this.render = () => {
    this.$element.innerHTML = `<input class="SearchInput__input" type="text" placeholder="프로그래밍 언어를 입력하세요." value="${this.state}">`;
  };

  this.render();

  //추가된 내용
  //이벤트 핸들러 구현부분
  this.$element.addEventListener("keyup", (e) => {
    onChange(e.target.value);
  });
}
//App.js
import { fetchLanguages } from "./api.js";
import SearchInput from "./SearchInput.js";

export default function App({ $target }) {
  this.state = {
    fetchedLanguages: [],
    selectedLanguages: [],
  };
  this.setState = (nextState) => {
    //TODO: 구현해야함
  };

//추가된 내용
  const searchInput = new SearchInput({
    $target,
    initialState: "",
    onChange: async (keyword) => {
      const languages = await fetchLanguages(keyword);
      console.log(languages);
    },
  });
}

 

API를 통해 데이터를 불러온 결과를 console에 찍힌다.

이제 위에서 얻어온 데이터를 통해 추천 검색어를 렌더링하고 키보드 화살표 키로 선택할 수 있도록 해보자.

 

 

Suggestion 구현

렌더링 구현

우선 현재 상태 기반으로 추천 검색어를 노출, setState를 통해 재렌더링하는 구조로 컴포넌트를 작성한다.

//Suggestion.js
export default function Suggestion({ $target, initialState }) {
  this.$element = document.createElement("div");
  this.$element.className = "Suggestion";
  $target.appendChild(this.$element);

  this.state = initialState;

  this.setState = (nextState) => {
    this.state = nextState;
    this.render();
  };

  this.render = () => {
    const { items = [] } = this.state;
    if (items.length > 0) {
      this.$element.style.display = "block";
      this.$element.innerHTML = `<ul> ${items
        .map((item, index) => `<li data-index="${index}">${item}</li>`)
        .join("")}</ul>`;
    } else {
      this.$element.style.display = "none";
      this.$element.innerHTML = "";
    }
  };

  this.render();
}

다음으로 App 컴포넌트에서 Suggestion 컴포넌트를 생성하도록 하고, App 컴포넌트의 setState 함수를 구현한다.

//App.js
import { fetchLanguages } from "./api.js";
import SearchInput from "./SearchInput.js";
import Suggestion from "./Suggestion.js";

export default function App({ $target }) {
  this.state = {
    fetchedLanguages: [],
    selectedLanguages: [],
  };

  this.setState = (nextState) => {
    this.state = {
      ...this.state,
      ...nextState,
    };
    suggestion.setState({
      items: this.state.fetchedLanguages,
    });
  };

  const searchInput = new SearchInput({
    $target,
    initialState: "",
    onChange: async (keyword) => {
      if (keyword.length === 0) {
        this.setState({
          fetchedLanguages: [],
        });
      } else {
        const languages = await fetchLanguages(keyword);
        this.setState({
          fetchedLanguages: languages,
        });
      }
    },
  });

  const suggestion = new Suggestion({
    $target,
    initialState: { items: [] },
  });
}

 

출력 결과


키보드로 추천 검색어의 커서 옮기기

키보드 방향키로 추천 언어 목록의 커서를 움직이게 하려고 한다.

현재 키가 어디를 순회하고 있는지를 확인하기 위해 selectedIndex 라는 값을 Suggestion 컴포넌트의 state에 추가한다.

export default function Suggestion({ $target, initialState }) {
  this.$element = document.createElement("div");
  this.$element.className = "Suggestion";
  $target.appendChild(this.$element);

  this.state = {
    selectedIndex: 0,
    items: initialState.items,
  };

  this.setState = (nextState) => {
    this.state = {
      ...this.state,
      ...nextState,
    };
    this.render();
  };

  this.render = () => {
    const { items = [] } = this.state;
    if (items.length > 0) {
      this.$element.style.display = "block";
      this.$element.innerHTML = `<ul> ${items
        .map((item, index) => `<li data-index="${index}">${item}</li>`)
        .join("")}</ul>`;
    } else {
      this.$element.style.display = "none";
      this.$element.innerHTML = "";
    }
  };

  this.render();
}

 

App 컴포넌트 내에서도 Suggestion의 상태를 변경할 시에 selectedIndex를 넣어주도록 한다.

//App.js
import { fetchLanguages } from "./api.js";
import SearchInput from "./SearchInput.js";
import Suggestion from "./Suggestion.js";

export default function App({ $target }) {
  this.state = {
    fetchedLanguages: [],
    selectedLanguages: [],
  };

  this.setState = (nextState) => {
    this.state = {
      ...this.state,
      ...nextState,
    };
    suggestion.setState({
      selectedIndex: 0,
      items: this.state.fetchedLanguages,
    });
  };

  const searchInput = new SearchInput({
    $target,
    initialState: "",
    onChange: async (keyword) => {
      if (keyword.length === 0) {
        this.setState({
          fetchedLanguages: [],
        });
      } else {
        const languages = await fetchLanguages(keyword);
        this.setState({
          fetchedLanguages: languages,
        });
      }
    },
  });

  const suggestion = new Suggestion({
    $target,
    initialState: {
      cursor: 0,
      items: [],
    },
  });
}

 

selectedIndex를 이용해 강조 처리

Suggestion__item--selected 클래스를 이용해 커서 처리를 한다.

 

Suggestion 컴포넌트의 render 함수를 아래처럼 바꿔준다.

//Suggestion.js
export default function Suggestion({ $target, initialState }) {
  this.$element = document.createElement("div");
  this.$element.className = "Suggestion";
  $target.appendChild(this.$element);

  this.state = {
    selectedIndex: 0,
    items: initialState.items,
  };

  this.setState = (nextState) => {
    this.state = {
      ...this.state,
      ...nextState,
    };
    this.render();
  };

  this.render = () => {
    const { items = [], selectedIndex } = this.state;
    if (items.length > 0) {
      this.$element.style.display = "block";
      this.$element.innerHTML = `<ul> ${items
        .map(
          (item, index) =>
            `<li class="${
              index === selectedIndex ? "Suggestion__item--selected" : ""
            }" data-index="${index}">${item}</li>`
        )
        .join("")}</ul>`;
    } else {
      this.$element.style.display = "none";
      this.$element.innerHTML = "";
    }
  };

  this.render();
}

 

출력 결과

' > 프론트엔드 직군 과제' 카테고리의 다른 글

프로그래밍 언어 검색 -3  (1) 2022.08.27
프로그래밍 언어 검색 -2  (0) 2022.08.26