최근 이직한 회사에서 php로 제작된 레거시 프로젝트를 담당하게 되었습니다.
프론트엔드 개발자 입장에서 백엔드 코드,DOM 조작 코드,HTML 코드 등이 뒤섞여있는 PHP 환경은 여간 불편한일이 아닙니다.
이를 개선하기 위해서 기존 프로젝트의 DOM 코드들을 web component로 적용하고 있습니다.
거의 매번 react로된 환경에서만 작업을 해오면서 생각할필요가 없어던 부분들을 고려해야 되다 보니 react의 장점이 느껴져서 둘을 비교하면서 정리해보겠습니다.
웹컴포넌트 web component
const html = String.raw;
class Modal extends HTMLElement {
constructor() {
super();
this.list = [];
this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.render();
this.renderList();
this.handleEvent();
}
disconnectedCallback() {}
renderList() {
if (this.list && this.list.length > 0) {
const listHTML = this.list.map(({ name }) => {
return html`<li>${name}</li>`;
});
this.shadowRoot.querySelector("#list").innerHTML = listHTML.join("");
} else {
this.shadowRoot.querySelector("#list").innerHTML = "";
}
this.renderCount();
}
renderCount() {
const currentCount = Array.from(
this.shadowRoot.querySelectorAll("#list li"),
).length;
this.shadowRoot.querySelector(
"#count",
).innerText = `(${currentCount}/${this.list.length})`;
}
handleEvent() {}
setList(selectedData) {
this.list = selectedData;
this.renderList();
}
show() {
this.modalContainer.show();
}
hide() {
this.modalContainer.hide();
}
render() {
this.shadowRoot.innerHTML = html` <style>
</style>`;
const modalContent = html`
<div class="modal" tabindex="-1">
<p>
<input class="form-check-input" type="checkbox" value="" />
<label>
<span>전체</span>
<span id="count">(0/${this.list.length})</span>
</label>
</p>
</div>
<ul id="list"></ul>
</div>
</div>
</div>
`;
const container = document.createElement("div");
container.innerHTML = modalContent;
this.shadowRoot.appendChild(container);
this.modalContainer = new bootstrap.Modal(
container.querySelector(".modal"),
);
}
}
customElements.define("custom-modal", Modal);
bootstrap을 사용하는 modal을 webcomponent로 작성한 경우 입니다.
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML
이렇게 shadow dom를 활용하면 style과 dom을 캡슐화해서 사용할 수 있습니다.
shadow dom 내부에서 작성한 style은 다른 html 코드에 영향을 주지 않습니다.
dom에서 사용하는 값이 변경될때 dom을 업데이트 해줘야 할때
setList(selectedData) {
this.list = selectedData;
this.renderList();
}
renderList() {
if (this.list && this.list.length > 0) {
const listHTML = this.list.map(({ name }) => {
return html`<li>${name}</li>`;
});
this.shadowRoot.querySelector("#list").innerHTML = listHTML.join("");
} else {
this.shadowRoot.querySelector("#list").innerHTML = "";
}
this.renderCount();
}
webcomponent나 일반적인 javascript의 경우 이런식으로 DOM을 직접 제어해야됩니다.
상태값이 변경될때마다 webcomponent dom을 새로 그리게 된다면
1. DOM 재구성 비용
innerHTML
을 사용하여 내용을 변경할 때마다 브라우저는 해당 HTML 문자열을 파싱하고, 기존 DOM 요소들을 제거한 후 새로운 요소들을 생성합니다. 이 과정은 비용이 많이 들며, 특히 복잡한 내용을 자주 업데이트하는 경우 렌더링 성능에 큰 영향을 줄 수 있습니다.
2. 레이아웃 재계산과 리플로우
DOM이 변경될 때마다 브라우저는 변경 사항을 반영하기 위해 레이아웃을 재계산하고, 필요한 경우 전체 페이지 또는 일부분의 리플로우(재배치 및 재그리기)를 수행해야 합니다. innerHTML
을 자주 사용하면 이러한 과정이 반복되어 페이지의 반응성이 저하될 수 있습니다.
3. 이벤트 리스너와 바인딩 손실
innerHTML
을 사용하여 HTML을 업데이트하면, 기존 요소들에 연결된 이벤트 리스너나 데이터 바인딩이 손실됩니다. 따라서 새 요소에 대해 이벤트 리스너를 다시 설정해야 하는 번거로움이 있으며, 이는 추가적인 성능 비용을 초래합니다.
4. 메모리 사용과 가비지 컬렉션
빈번한 DOM 업데이트는 오래된 DOM 요소들을 메모리에서 제거하고 새 요소를 생성하는 과정을 반복하게 만듭니다. 이로 인해 가비지 컬렉션 활동이 증가하고, 결과적으로 메모리 사용량이 늘어날 수 있습니다.
위와 같은 문제가 생길수 있습니다.
UI(DOM)을 변경하기 위해서
renderCount() {
const currentCount = Array.from(
this.shadowRoot.querySelectorAll("#list li"),
).length;
this.shadowRoot.querySelector(
"#count",
).innerText = `(${currentCount}/${this.list.length})`;
}
위처럼 상태가 바뀌면 일일히 적용되는 DOM을 찾아서 제어해주어야 합니다.
React
import React, { useState, useEffect } from 'react';
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
const CustomModal = ({ selectedData }) => {
const [list, setList] = useState(selectedData || []);
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const totalCount = list.length;
const currentCount = list.filter(item => item.checked).length;
return (
<Modal show={show} onHide={handleClose} centered>
<Modal.Header closeButton>
<Modal.Title>Custom Modal</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
<input className="form-check-input" type="checkbox" value="" />
<label>
<span>전체</span>
<span id="count">({currentCount}/{totalCount})</span>
</label>
</p>
<ul id="list">
{list.map((item, index) => (
<li key={index}>{item.name}</li>
))}
</ul>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
<Button variant="primary" onClick={handleClose}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
);
};
export default CustomModal;
이를 react component로 작성하면 위와 같습니다.
react에서 상태 값(state)가 변경되면 자동으로 dom이 변경되는 부분을 찾아서 dom을 변경시켜 줍니다.
덕분에 개발자는 dom을 직접제어하는 일이 없습니다
React에서 말하는 Virtual DOM과 state 관리에 대해서 와닿는 계기가 되었습니다.
결론 : React 짱.
'개발관련 > 리액트' 카테고리의 다른 글
nextjs 14 app router에서 custom api 작성을 위한 Route Handlers (0) | 2024.01.24 |
---|---|
React는 virtual-dom인가? (0) | 2023.08.27 |
React는 rendering 중에 계산하는 것이 더 빠르다. (0) | 2023.08.20 |
react에서 Windowing을 구현해보자 (0) | 2023.08.10 |
[번역] 리액트 기술을 한 단계 업그레이드하세요: 2023년에 마스터할 고급 패턴 5가지 (0) | 2023.08.07 |