JavaScript 뷰를 어려운 방식으로 구축하기 - UI 작성 패턴

6 days ago 7

  • Writing JavaScript Views the Hard Way : 프레임워크 없이 순수 자바스크립트로 뷰를 구축하는 방법을 설명하는 글
  • 직접적인 명령형 접근 방식을 통해 성능, 유지보수성, 이식성을 확보함
  • 상태 업데이트와 DOM 업데이트를 명확히 구분하고, 각 역할에 따라 엄격한 명명 규칙과 구조적 패턴을 따름
  • 이 방식은 디버깅이 쉬우며, 모든 브라우저 호환성을 보장하고, 0 dependencies라는 큰 장점이 있음
  • 초보자에게는 어려울 수 있지만, 학습 시 실제 시스템 작동 방식에 대한 깊은 이해를 제공함

자바스크립트 뷰를 'Hard Way'로 작성하기

이것이 무엇인가?

  • 이 방식은 React, Vue, lit-html과 같은 프레임워크 없이 자바스크립트만으로 뷰를 구성하는 패턴
  • 특정 라이브러리나 도구가 아닌 코딩 패턴 자체로, 스파게티 코드 문제를 방지함
  • 직접적인 명령형 방식을 사용함으로써, 추상화를 줄이고 직관성을 높임

프레임워크 대비 장점

  • 성능: 명령형 코드로 인해 불필요한 연산 없이 동작하며, 핫패스와 콜드패스 모두에 적합함
  • 0 dependencies: 라이브러리 업그레이드나 호환성 문제로부터 자유로움
  • 이식성: 작성한 코드가 어느 프레임워크에도 이식 가능함
  • 유지보수성: 명확한 섹션 구조와 명명 규칙으로 코드 위치 파악이 쉬움
  • 브라우저 지원: IE9 이상 대부분 브라우저와 호환되며, IE6까지도 일부 수정으로 지원 가능함
  • 디버깅 용이성: 중간 레이어 없이 얕은 스택 트레이스를 제공
  • 함수형 구조: 불변성은 아니지만, 모든 구성 요소가 함수 기반으로 구성됨

구조 설명

전체 구조

  • template → clone() → init() 함수로 구성됨
  • init() 함수는 상태 변수, DOM 참조, 업데이트 함수, 이벤트 리스너 등을 포함한 하나의 뷰 인스턴스를 생성

예시 코드 구조 (Hello World)

const template = document.createElement('template'); template.innerHTML = `<div>Hello <span id="name">world</span>!</div>`; function clone() { return document.importNode(template.content, true); } function init() { let frag = clone(); let nameNode = frag.querySelector('#name'); let name; function setNameNode(value) { nameNode.textContent = value; } function setName(value) { if(name !== value) { name = value; setNameNode(value); } } function update(data = {}) { if(data.name) setName(data.name); return frag; } return update; }

init() 함수 내부 구성

1. DOM 변수

  • frag는 clone()으로부터 생성된 템플릿 조각
  • 내부 요소는 querySelector()로 참조하고, 변수명은 fooNode 형태 사용

2. DOM 뷰

  • 다른 뷰를 포함하는 부분 (재사용 가능한 서브 뷰)
  • 예시:
let updateChildView = childView();
  • 뷰 업데이트 함수는 updateFoo 형태로 명명

3. 상태 변수

  • 뷰 내에서 변경될 수 있는 데이터 값
  • DOM 업데이트를 효율적으로 하기 위해 현재 값과 비교하여 필요할 때만 DOM 변경

4. DOM 업데이트 함수

  • DOM 요소의 상태를 변경할 때 사용
  • 예시:
function setNameNode(value) { nameNode.textContent = value; }
  • DOM 조작은 반드시 이 함수 안에서만 수행

5. 상태 업데이트 함수

  • 상태 변경 로직과 그에 따른 DOM 반영 포함
  • 변경되지 않은 값은 무시하여 불필요한 DOM 변경 방지
  • 예시:
function setName(value) { if(name !== value) { name = value; setNameNode(value); } }

template과 clone() 함수

template

  • <template> 요소로 정적 HTML 구조 생성
  • DOM에 직접 삽입되지 않으며, clone을 통해 복사본 생성

clone()

  • document.importNode(template.content, true)로 복제
  • 필요시 .firstElementChild를 사용하여 루트 요소 반환 가능

상호작용 방식

부모 → 자식 데이터 흐름

  • 부모는 자식의 init()을 호출하여 업데이트 함수를 획득하고, update({ name: 'foo' }) 형식으로 호출

이벤트 기반 데이터 전파

  • 기본적으로 props down, events up 모델 따름
  • 하위 뷰는 이벤트를 상위로 디스패치하여 통신

React와 비교

  • constructor() (React)init() (Hard Way)
    • 컴포넌트 초기 설정을 담당함
  • render() (React)update(data) (Hard Way)
    • 화면 갱신 및 UI 업데이트 역할 수행
  • this.setState() (React)setX(value) (Hard Way)
    • 상태값을 직접 설정하는 방식으로 대체됨
  • props (React)update(data)로 전달된 값 (Hard Way)
    • 부모 컴포넌트로부터 전달된 데이터 처리 방식
  • JSX / Virtual DOM (React)HTML 템플릿 + DOM API (Hard Way)
    • 선언형 UI 대신 수동적인 DOM 조작 및 템플릿 사용

결론

  • 이 방식은 익숙한 프레임워크에 비해 초기 진입 장벽은 높지만, 다음과 같은 강점을 가짐:
    • 성능 최적화
    • 완전한 제어권
    • 학습을 통한 깊은 이해
  • 각 역할별 함수 분리 및 명명 규칙을 통해, 프레임워크 없이도 유지보수 가능한 UI 구성 가능

호환성

  • 최신 예시는 현대 브라우저용 API를 사용하지만, IE9 이하까지도 함수 기반 대체를 통해 지원 가능
  • 이벤트 대신 props로 함수 전달을 사용하는 방식으로 IE6까지도 확장 가능

Read Entire Article