Vanilla JS에서 DocumentFragment로 성능 최적화

seongjin·2023년 3월 30일
0

DocumentFragment란?

DocumentFragment 인터페이스는 아주 작고 부모를 갖지 않는 문서 객체를 나타냅니다. DocumentFragment는 일반 문서처럼 노드로 구성된 문서 구조를 저장할 수 있으므로 Document의 가벼운 버전으로 사용됩니다. Document와의 중요한 차이점으로, DocumentFragment는 활성화된 문서 트리 구조의 일부가 아니기 때문에 내부의 트리를 변경해도 문서나 성능에 아무 영향도 주지 않으며 리플로우도 방지할 수 있습니다.

적용한 부분


async setProductList() {
        await this.getProductData(); //  api요청 함수
        this.mainElement.classList.add("product");

        const productPageHeader = document.createElement('h1');
        productPageHeader.setAttribute('class', 'ir');
        productPageHeader.innerText = '상품목록 페이지';
        this.mainElement.appendChild(productPageHeader);

        const productList = document.createElement('ul');
        productList.setAttribute('class', 'product-list');

		// 문제 상황
        this.product.forEach((item) => {
            const productItem = document.createElement('li');
            productItem.setAttribute('class', 'product-item');
            const productCard = new ProductCard(item);

            productItem.append(productCard.render());
            productList.appendChild(productItem);
        });
        
        this.mainElement.appendChild(productList)
    }

해당 코드는 api요청을 받아온 후 배열 상태인( [ item1, item2, item3 ...] ) this.product를 순회하면서 li을 만들고 ul태그에 추가해주고 있다. 여기서 문제점은 리플로우가 일어난다는 것이다.

리플로우(reflow)란?

DOM 요소의 기하학적 속성이 변경될때, 브라우저 사이즈가 변할때, 스타일시트가 로딩되었을때 발생하는 변화들을 다시 계산 해주는 작업을 뜻하고 레이아웃(Layout) 이라고도 한다. 이런 요인에 의해 변화된 요소 주변의 모든 (부모, 자식, 형제) 요소들도 영향을 받게되는데 결국 DOM 요소의 하나의 시각적 변화가 DOM 트리 전체에 대해 다시 계산을 수행하게 된다.

브라우저 렌더링 과정에서 DOM API 등으로 인해 DOM Tree, CSSOM에 변경이 생기게 되면 렌더트리를 다시 구성하게 된다. 단순히 요소 하나만 바뀌어도 그것이 레이아웃에 영향을 주게되면 주변 요소들을 고려해서 화면을 전부 다시 그리게 되는 것이다. 이것을 리플로우라 한다.

수정 후 코드
async setProductList() {
        await this.getProductData();
        this.mainElement.classList.add("product");

        const productPageHeader = document.createElement('h1');
        productPageHeader.setAttribute('class', 'ir');
        productPageHeader.innerText = '상품목록 페이지';
        this.mainElement.appendChild(productPageHeader);

        const productList = document.createElement('ul');
        productList.setAttribute('class', 'product-list');
  		
  // 적용부분
        const $fragment = document.createDocumentFragment();

        this.product.forEach((item) => {
            const productItem = document.createElement('li');
            productItem.setAttribute('class', 'product-item');
            const productCard = new ProductCard(item);

            productItem.append(productCard.render());
            $fragment.appendChild(productItem);
        });
        
        productList.appendChild($fragment);
        this.mainElement.appendChild(productList)
    }

바로 ul태그의 자식으로 li를 추가하는 것이 아니라 DocumentFragment 객체를 생성해서 여기에 li를 추가하고 한 번에 ul태그에 추가해주는 것이다. 이렇게 되면 리플로우는 1번만 일어나게 된다.

그럼 여기서 의문점이 들 수 있을것이다. '똑같이 div태그를 생성하고 div에 append 하면 똑같이 1번만 리플로우가 일어나는 것이 아니냐?' 라고 생각할 수 있는데 documentFragment는 경량화된 docuement객체이자, DOM tree 구조의 일부가 아니다라고 했다. 이것을 이용하면 다른 방법보다 속도도 빠르고 DOM tree에 렌더링 되지 않는다. 즉 쓸데없는 div태그가 생성되지 않아 성능면에서 documentFragment가 낫다.

결론

리액트만 쓰다가 이번에 바닐라 js로 간단한 쇼핑몰을 만들어 보면서 공부중인데 리액트에는 Virtual DOM이라는 개념이 있어 자동으로 리플로우를 최소화 해준다. 사실 리액트를 공부하면서 가상돔이라는 개념을 알긴 알았는데, 직접 바닐라 js에서 리플로우 관련 성능최적화를 해보니 리액트가 얼마나 편리한지 더욱 깨닫는 중이다..ㅎㅎ 하지만 이번 바닐라 js를 공부하는 이유도 특정 라이브러리나 프레임워크를 무작정 쓰기보다는 기초가 되는 Vanilla JS를 연습하고 리액트가 주는 편리성과 차이점을 느껴보고 싶었다. 역시 직접 체감을 해봐야 프레임워크나 라이브러리가 얼마나 편한지 알게되는 것 같다!!

profile
나만의 오답노트

0개의 댓글