Notice
Recent Posts
Recent Comments
관리 메뉴

즐겁게, 코드

TreeWalker 객체로 DOM을 순회하며 필터 적용하기 본문

🎨 프론트엔드

TreeWalker 객체로 DOM을 순회하며 필터 적용하기

Chamming2 2024. 12. 7. 13:09

개발을 진행하다 보면 문자열화된 DOM을 마크업에 렌더링해야 하는 경우가 종종 생기기 마련입니다.

💡 문자열 형태의 DOM을 purify하는 이유는 DOMPurify 라이브러리에 잘 소개되어 있습니다.
import parse from "html-react-parser";
const App = () => {
  const purifiedString = parse("<div>hello world</div>");

  return <main>{string}</main>;
};

export default App;

최근 진행하던 프로젝트에서 관련된 문제가 있었는데, 어떻게 문제를 해결했는지를 소개합니다.

📝 TL;DR : DOM을 순회하며 탐색하거나 필터해야 할 때는 TreeWalker 객체를 사용해 보세요.

먼저 흔한 모습은 아니지만, 모종의 이유로 백엔드에서 문자열 형태의 DOM을 내려주어 사용해야 하는 경우가 있다고 가정하겠습니다.

import parse from "html-react-parser";

const App = () => {
  const MOCK_NEWS_DATA = [
    "<div>[속보]: 이제 벌써 연말 다가와... 30대를 바라보고 있어</div>",
    "<div>[경제]: 금리 인하 결정... 서민 부담 줄어드나</div>",
    "<div>[IT]: 앗, 맥미니 타이어 20개보다 싸다!</div>",
  ];

  return (
    <main>
      <ul>
        {MOCK_NEWS.map((news) => (
          <li key={news}>{parse(news)}</li>
        ))}
      </ul>
    </main>
  );
};

export default App;

"한줄 뉴스" 라는 서비스를 구현하게 되어 DOM을 Purify한 뒤 화면에 그려준 모습입니다.

여기까지는 문제가 없는데, 만약 텍스트가 아닌 요소가 문자열에 포함되어 있다면 어떨까요?

import parse from "html-react-parser";

const App = () => {
  const MOCK_NEWS = [
    "<div>[속보]: 이제 벌써 연말 다가와... 30대를 바라보고 있어</div>",
    "<div><img src='https://placehold.co/300x200'/>[IT]: 내수 위주의 한국 IT, 해법은 무엇인가...</div>",
    "<div>[경제]: 금리 인하 결정... 서민 부담 줄어드나</div>",
    "<div>[게임]: 3년만의 플레이스테이션 신작 발표 및 티저 공개 <img src='https://placehold.co/500x400' />...</div>",
    "<div>[IT]: 앗, 맥미니 타이어 20개보다 싸다!</div>",
  ];

  return (
    <main>
      <div>한줄 뉴스</div>
      <ul>
        {MOCK_NEWS.map((news) => (
          <li key={news}>{parse(news)}</li>
        ))}
      </ul>
    </main>
  );
};

export default App;

텍스트에 이미지 태그가 포함되면서, 당연하게도 레이아웃이 깨지는 모습입니다.

이런 상황은 어떻게 피할 수 있을까요?

TreeWalker 객체

DOM 트리를 이루는 기본 요소가 Node 라면, TreeWalker(MDN)는 DOM의 Node를 순회하거나 필터할 수 있는 기능을 제공하는 객체입니다.

예시

const treeWalkerRoot = document.createElement("div");
treeWalkerRoot.innerHTML = "<div>[게임]: 3년만의 플레이스테이션 신작 발표 및 티저 공개 <img src='https://placehold.co/500x400' />...</div>";

// const treeWalker = document.createTreeWalker(<순회를 시작할 노드>, <필터할 노드 유형>, <추가 필터 조건>);
// 텍스트만 필터하는 예시
const treeWalker = document.createTreeWalker(treeWalkerRoot, NodeFilter.SHOW_TEXT);
💡 각 NodeFilter 상수는 window 객체에 바인딩되어 있으며, 저마다 다른 값을 갖고 있습니다.
(Ex. NodeFilter.SHOW_ALL = 4294967295)

NodeFilter 상수의 종류는 이 문서(MDN)를 참고해 주세요.

이제 TreeWalker 객체로 DOM 문자열에서 텍스트만을 필터해 보겠습니다.

const getFirstTextNode = (articleText: string): string => {
  const treeWalkerRoot = document.createElement("div");
  treeWalkerRoot.innerHTML = articleText;

  const treeWalker = document.createTreeWalker(
    treeWalkerRoot,
    NodeFilter.SHOW_TEXT, // 텍스트 노드만 선택
    {
      // acceptNode는 Array.filter처럼 필터할 노드를 선택할 조건을 다루는 함수입니다.
      acceptNode: function (node: Node) {
        // 공백이 아닌 텍스트 노드만을 허용
        if (node.nodeValue && node.nodeValue.trim().length > 0) {
          return NodeFilter.FILTER_ACCEPT;
        }
        return NodeFilter.FILTER_REJECT;
      },
    }
  );

  // 첫 번째 텍스트 노드를 반환
  const textNode = treeWalker.nextNode();
  return textNode ? textNode.nodeValue!.trim() : "";
};
  
const articleText = `<div>[게임]: 3년만의 플레이스테이션 신작 발표 및 티저 공개 <img src='https://placehold.co/500x400' />...</div>`

// [게임]: 3년만의 플레이스테이션 신작 발표 및 티저 공개
console.log(getFirstTextNode(articleText));


텍스트가 깔끔하게 파싱된 모습입니다. 

import { getFirstTextNode } from "./getFirstTextNode";

const App = () => {
  const MOCK_NEWS = [
    "<div>[속보]: 이제 벌써 연말 다가와... 30대를 바라보고 있어</div>",
    "<div><img src='https://placehold.co/300x200' />[IT]: 내수 위주의 한국 IT, 해법은 무엇인가...</div></div>",
    "<div>[경제]: 금리 인하 결정... 서민 부담 줄어드나</div>",
    "<div>[게임]: 3년만의 플레이스테이션 신작 발표 및 티저 공개 <img src='https://placehold.co/500x400' />...</div>",
    "<div>[IT]: 앗, 맥미니 타이어 20개보다 싸다!</div>",
  ];

  return (
    <main>
      <div>한줄 뉴스</div>
      <ul>
        {MOCK_NEWS.map((news) => (
          <li key={news}>{getFirstTextNode(news)}</li>
        ))}
      </ul>
    </main>
  );
};

export default App;

이제 DOM 문자열에 <img>, <video> 등의 요소가 섞여 있더라도 텍스트 요소만을 깔끔하게 표시할 수 있게 된 모습입니다.

 

TreeWalker 객체가 없었다면 `<` 문자열을 포함하고 있는지 등을 복잡하게 검사해야 했을 텐데, DOM을 순회하며 특정 조건에 따라 필터해야 한다면 TreeWalker 객체를 사용할 수 있음을 기억해주시면 좋을 것 같습니다.

 

그럼, 오늘도 즐거운 코딩 하세요!

 

참고

https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker#instance_methods

 

TreeWalker - Web API | MDN

TreeWalker 오브젝트는 도큐먼트 서브트리의 노드들과 그 안에서의 위치를 나타낸다.

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/API/Document/createTreeWalker#whattoshow

 

Document.createTreeWalker() - Web API | MDN

Document.createTreeWalker() 메소드는 새로운 TreeWalker 객체를 반환합니다.

developer.mozilla.org

 

반응형
Comments
소소한 팁 : 광고를 눌러주시면, 제가 뮤지컬을 마음껏 보러다닐 수 있어요!
와!! 바로 눌러야겠네요! 😆