본문 바로가기
etc/[프로그래머스] 데브매칭

[프로그래머스/2022 상반기 프론트엔드 데브매칭] 프로그래밍 언어 검색 답안코드 + 코드 분석

by 1two13 2023. 2. 23.
728x90
반응형

제공되는 정답지(?)보다 더 깔끔하게 코드를 작성할 수 있을 거 같아서 도움이 되면 좋을 거 같아서 코드 첨부합니다.

저는 class를 사용하여 코드를 작성했습니다. 

 

 

 

문제 


프로그래머스 > 코딩 테스트 연습 > 과제 테스트 연습 > 프로그래밍 언어 검색

 

index.html부터 코드 흐름을 읽어나가면 됩니다!

728x90

 

 

 

답변 코드


// index.html

<html>
  <head>
    <title>2022 FE 데브매칭</title>
    <link rel="stylesheet" href="./style.css">
  </head>
  <body>
    <main class="App">
    </main>
    <script type="module" src="./index.js"></script>
  </body>
</html>

 

// index.js

import App from './App.js';

new App(document.querySelector('.App')).render();

 

// src/api/api.js

const BASE_URL = 'https://wr4a6p937i.execute-api.ap-northeast-2.amazonaws.com/dev';

// keyword를 query로 받아서 사용해야하기 때문에 async/awiat으로 비동기처리를 했다.
export const fetchedApi = async (keyword) => {
  // fetch 앞에 await을 붙여야 한다.
  let res = await fetch(`${BASE_URL}/languages?keyword=${keyword}`);

  // return 할 때도 await을 붙여야 한다.
  if (res.ok) return await res.json();
  // 에러가 날 때 인지할 수 있게 new Error 객체를 사용하여 에러 메세지를 줬다.
  throw new Error('요청 실패');
};

 

// src/SearchInput.js

class SearchInput {
  #element;

  constructor(target, initialState, onChange) {
    this.#element = document.createElement('form');
    this.#element.className = 'SearchInput';
    target.appendChild(this.#element);
    this.state = initialState;

    this.#element.addEventListener('keyup', (e) => {
      const actionIgnoreKeys = ['Enter', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
      // 화살표, 엔터키의 이벤트가 발생하는 경우 제외
      if (!actionIgnoreKeys.includes(e.key)) onChange(e.target.value);
    });
    // form은 input에 focus가 있는 상태에서 enter키를 입력하면 form의 action에 지정된 url로 화면을 이동하려고 한다.
    // 그래서 이를 방지하기 위해 preventDefault를 호출했다.
    this.#element.addEventListener('submit', (e) => e.preventDefault());
  }

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

export default SearchInput;
반응형

 

// src/Suggestion.js

class Suggestion {
  #element;

  constructor(target, initialState, onSelect) {
    this.#element = document.createElement('div');
    this.#element.className = 'Suggestion';
    target.appendChild(this.#element);
    // 첫 화면에서 suggestion 보여주지 않기
    this.#element.style.display = 'none';
    this.state = initialState;

    window.addEventListener('keyup', (e) => {
      if (this.state.items.length > 0) {
        const { selectedIndex } = this.state;
        const lastIndex = this.state.items.length - 1;
        const navigationKeys = ['ArrowUp', 'ArrowDown'];
        let nextIndex = selectedIndex;

        if (navigationKeys.includes(e.key)) {
          // 위쪽 화살표 키를 누르면
          if (e.key === 'ArrowUp') {
            nextIndex = selectedIndex === 0 ? lastIndex : nextIndex - 1;
          } else if (e.key === 'ArrowDown') {
            nextIndex = selectedIndex === lastIndex ? 0 : nextIndex + 1;
          }
          this.setState({
            ...this.state,
            selectedIndex: nextIndex,
          });
          // 엔터 키를 누르면
        } else if (e.key === 'Enter') {
          onSelect(this.state.items[this.state.selectedIndex]);
        }
      }
    });

    this.#element.addEventListener('click', (e) => {
      // li를 찾을 때까지 자기 자신을 포함해 위쪽(부모 방향)으로 문서 트리를 순회하는 closest
      const li = e.target.closest('li');

      if (li) {
        // dataset 객체를 통해 data- 의 뒷부분을 가져올 수 있다.
        const { index } = li.dataset;
        try {
          onSelect(this.state.items[parseInt(index)]);
        } catch (e) {
          alert('[오류] 선택할 수 없습니다.');
        }
      }
    });
  }

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

    this.render();
  }

  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 = '';
    }
  }
}

export default Suggestion;

 

// src/selectedLanguages.js

class selectedLanguages {
  #element;

  constructor(target, initialState) {
    this.#element = document.createElement('div');
    this.#element.className = 'SelectedLanguage';
    target.appendChild(this.#element);
    this.state = initialState;
  }

  setState(nexState) {
    const MAX_DISPLAY_COUNT = 5;
    this.state = nexState;

    if (this.state.length > MAX_DISPLAY_COUNT) {
      const startPosition = this.state.length - MAX_DISPLAY_COUNT;
      this.state = this.state.slice(startPosition, MAX_DISPLAY_COUNT + startPosition);
    }

    this.render();
  }

  render() {
    this.#element.innerHTML = `
          <ul>
              ${this.state.map((item) => `<li>${item}</li>`).join('')}
          </ul>`;
  }
}

export default selectedLanguages;

 

// App.js

import SearchInput from './src/SearchInput.js';
import Suggestion from './src/Suggestion.js';
import SelectedLanguage from './src/SelectedLanguages.js';
import { fetchedApi } from './src/api/api.js';

class App {
  constructor(target) {
    this.state = {
      fetchedLanguages: [],
      selectedLanguages: [],
    };
    this.selectedLanguages = new SelectedLanguage(target, []);
    this.searchInput = new SearchInput(target, '', async (keyword) => {
      if (keyword.length === 0) this.setState({ fetchedLanguages: [] });
      else this.setState({ fetchedLanguages: await fetchedApi(keyword) });
    });
    this.suggestion = new Suggestion(target, { selectedIndex: 0, items: [] }, (language) => {
      alert(language);

      const nextSelectedLanguages = [...this.state.selectedLanguages];
      const index = nextSelectedLanguages.findIndex(
        (selectedLanguages) => selectedLanguages === language
      );

      // index를 찾은 경우
      if (index > -1) nextSelectedLanguages.slice(index, 1);
      // 해당 언어 push
      nextSelectedLanguages.push(language);

      this.setState({ ...this.state, selectedLanguages: nextSelectedLanguages });
    });
  }

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

  render() {
    this.searchInput.render();
  }
}

export default App;

 

내가 기억하려고 쓰는 포인트

1. index.js 파일 생성하고 index.html에 script 태그로 연동하기

2. App.js 파일 생성하기 (App에서 다른 클래스를 불러와서 사용하기)

3. index.js 파일에서 new App() 객체 렌더링하기 

 

 

 

 

 

 


질문이나 잘못된 점은 댓글로 남겨주세요 :)💖

 

 

 

 

 

728x90
반응형

댓글