같은 className의 요소들을 순회하며 특정 동작을 수행해야 할 때가 있다.
const balls = document.getElementsByClassName("ball");
balls.forEach((element) => {}); // Error!
위와 같이 작성하면 될 것 같지만...
HTMLCollectionOf 에는 forEach가 없단다. 왜?
https://developer.mozilla.org/ko/docs/Web/API/HTMLCollection
당연히 없으니까 없다.
HTMLCollection은 요소의 문서 내 순서대로 정렬된 유사 배열이며,
특정 요소를 선택하는데 필요한 메서드을 별로도 제공하고 있다.
const balls = document.getElementsByClassName("ball");
balls.length; // number
balls[1]; // Element
balls.item(1); // Element
즉 위와 같은 속성에는 접근 가능하지만,
배열에만 존재하는 forEach, map, filter... 등의 메서드는 사용할 수 없다.
그렇다면 className으로 조회한 요소들을 순회하기 위해서는 어떻게 해야 할까?
1. for 문으로 순회
const balls = document.getElementsByClassName("ball");
for (let i = 0; i <= balls.length; i++) {
balls[i];
}
2. Array로 변환한 후 순회
const balls = document.getElementsByClassName("ball");
Array.from(balls).forEach((ball) => {
});
위 두 방법 다 정상적으로 동작하고, 대부분 위의 방법을 추천하는데...
여기서 문제는? getElementsByClassName이 반환하는 값이 타입이 HTMLCollectionOf <Element>라는 것!
위 상황에서 class name을 통해 각 요소들을 조회한 이유는
대부분 각 html 요소의 속성에 접근하기 위함일 테다! (가장 대표적으로 style이 있겠다)
하지만 해당 속성들은 대부분 Element를 상속받은 HtmlElement 타입에 정의되어 있기 때문에...
const balls = document.getElementsByClassName("ball");
Array.from(balls).forEach((ball) => {
ball.style.backgroun = 'red' // Error!
});
위 코드는 다음과 같은 에러가 발생한다.
(1번, 2번 방법 모두 마찬가지!)
const balls = document.getElementsByClassName(
"ball"
) as unknown as HTMLElement[];
Array.from(balls).forEach((ball) => {
ball.style.background = "red";
});
물론 위와 같이 강제 타입 형변환을 해도 동작은 하지만,
타입을 강제로 정의한다는 점에서... 만족스럽다고 볼 수는 없다. (as 쓸 거면 TS 쓰지 말자)
그렇다면 어쩌면 좋을까?
3. querySelectorAll을 통한 제네릭 지정
querySelectorAll을 사용하면 된다.
const balls = document.querySelectorAll(".ball");
엥? getElementsByClassName와 사용하는 방식만 다른 것 아니었나??
반환값부터 아예 다른 걸 확인할 수 있다.
https://developer.mozilla.org/ko/docs/Web/API/NodeList
NodeList 역시 HTMLCollection과 마찬가지로 유사 배열이지만,
구형 브라우저를 제외하고는 forEach 메서드를 통한 순회가 가능하다!
const balls = document.querySelectorAll(".ball");
balls.forEach((ball) => {
ball.style.background = "red"; // Error!
});
즉 위와 같이 작성해 줄 수 있는데...
여전히 동일한 오류가 발생한다. 당연!
하지만... 아까 querySelectorAll의 타입을 유심히 봤다면, 해결책을 금방 찾을 수 있다.
getElementsByClassName과 달리, 제네릭을 넘겨받는 모습!
querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;
실제 타입 구현은 다음과 같다.
아하! 따라서
const balls = document.querySelectorAll<HTMLElement>(".ball");
balls.forEach((ball) => {
ball.style.background = "red";
});
다음과 같이, HTMLElement 인터페이스를 제네릭으로 넘겨주면
배열 속 요소를 HTMLElement로 추론할 수 있게 된다!
'source-code > TypeScript' 카테고리의 다른 글
팩토리 구현 시, 인스턴스 타입 안전성 확보하기 (0) | 2024.07.05 |
---|---|
"'React'은(는) UMD 전역을 참조하지만 현재 파일은 모듈입니다" 에러 해결 (2) | 2023.11.18 |
react-hook-form TS와 똑똑하게 사용하기 (0) | 2023.08.21 |
index signature 관련 타입 에러 (0) | 2023.08.21 |
JsToTs : axios (0) | 2023.08.21 |