[TIL] NodeList

hanbyul.choi·2023년 5월 29일
0

[TIL]

목록 보기
10/39

영화 평점 TOP20 데이터를 open API로 불러와서 간단하게 보여주는 개인 프로젝트를 진행 중이였다.

필요한 필수 기능을 만들었지만 같은 구문을 다시 사용하는 과정이 많아 리팩토링을 해야했다.

그 중 검색기능 부분이 문제가 많아서 수정중이였다. 아래처럼 데이터 card들을 리스팅하는 부분을 재사용 했기 때문이었다.

  fetch(
    "https://api.themoviedb.org/3/movie/top_rated?language=en-US&page=1",
    options
  )
    .then((response) => response.json())
    .then((response) => {
      response["results"].forEach((i) => {
        let title = i["title"];
        let rate = i["vote_average"];
        let desc = i["overview"];
        let img = i["poster_path"];
        let id = i["id"];
        let url = `https://image.tmdb.org/t/p/w500/${img}`;

        let temp_html = ` <img class="poster" src="${url}" alt="movie">
                        <h3 class="movie-title">${title}</h3>
                        <p>${desc}
                        </p>
                        <footer class="rate"><p>Ration : ${rate}</p></footer>
                      `;
        // cardList 하위로 card 요소들 붙여넣기
        const $cardList = document.getElementById("card-list");
        const $card = document.createElement("div");
        $card.classList.add("card");
        $card.innerHTML = temp_html;
        $cardList.appendChild($card);

        // click 이벤트 적용
        $card.addEventListener("click", () => {
          alert(`영화 id : ${id}`);
        });
      });
    })

data를 다시 다 불러서 title값으로 필터링 한 배열을 다시 temp_html로 붙여넣기를 수행 하는 방식이었다.

리팩토링을 위해 키워드를 검색했을 때 필터링하는 데이터와 처음 접속시에 데이터를 따로 받아서 innerHTML을 적용하는 부분을 공통으로 쓸 수 있게 계획했다.

그렇게 진행하다 보니 너무 비효율적이라는 생각이 들었다.
굳이 다시 데이터를 받아가면서 지우고 다시 리스팅하는 것은 아닌 것 같다.

예전 Vanilla JS로 토이 프로젝트를 했을 때 class 속성 display :none;을 부여해서 데이터를 숨겼던 방식이 기억났다. 그래서 그 방식을 적용하기로 하고 검색기능을 싹 갈아 엎었다.


그러던 중 문제가 발생했다.

  const $card = document.querySelectorAll(".card");
  console.log($card);
  const sortList = $card.filter((i) => {
    return $card[i]
      .querySelector(".movie-title")
      .textContent.toLowerCase()
      .includes($value);
  });
  console.log(sortList);

모든 card 요소를 가져와서 필터링 한 뒤에 class="show"를 넣고 싶었다.

그러나

filter 메서드가 안써진다.
type을 확인해보니 $card는 Object.

찾아보니 전개구문을 사용하여 배열로 만들면 해결이 가능하다.
그 이유는 querySelectorAll의 리턴 값은 Dom Tree의 Node List이기 때문이다.

Node List의 간단한 개념을 설명하자면, 먼저 Node는 HTML의 부모요소와 자식요소, 그리고 자기자신의 관한 정보를 담고 있는 객체라고 할 수 있다.
그럼 Node List는 그런 Node의 집합이다.

따라서, querySelectorAll을 쓰면 여러개의 Node를 가져오는 메서드이기 때문에 Node List가 반환된다.


Node List의 메서드는 따로 있다.

NodeList.item() : 리스트 내 항목(item)의 인덱스를 반환하고, 인덱스가 범위 외의 경우일 땐 null을 반환.

NodeList.entries() : iterator 를 반환하여 코드가 콜렉션에 포함된 모든 키/값 쌍을 순회 (이 경우 키는 0부터 시작하는 숫자이고, 값은 각 노드.)

NodeList.keys() : iterator를 반환하여 콜렉션에 포함된 키/값 쌍의 모든 키를 코드가 순회 (즉, Key는 0부터 시작한다.)

NodeList.values() : 콜렉션에 포함된 키/값 쌍의 모든 값(nodes)을 코드가 순회할 수 있게 해주는 iterator를 반환합니다.

NodeList.forEach() : 요소(element)마다 한 번씩, 인자로 전달 받은 함수를 실행하여 요소를 인수(argument)로 함수에 전달 (배열 메서드와 동일)

Node의 개념과 메서드는 아래 링크에서 확인할 수 있음. https://developer.mozilla.org/ko/docs/Web/API/Node



결론적으론, filter 메서드를 사용할 수 없기 때문에 아래와 같이 전개구문으로 형변환하여 사용해야 한다.(형변환 하는 메서드는 많기 때문에 편한 것을 쓰면 됨)

  const $card = document.querySelectorAll(".card");
  console.log($card);
  const sortList = [...$card].filter((i) => {
    return !i
      .querySelector(".movie-title")
      .textContent.toLowerCase()
      .includes($value);
  });
  console.log(sortList);

이런 문제를 하나하나 해결하고 정리할 때마다 지식이 쌓인다.
학습하는 것보다 이렇게 마주치는게 더 기억에 잘 남는 것 같고 개념이해가 된다.

0개의 댓글