개요
최근 웹사이트 개발을 하면서 DOM level1 이벤트 모델에 대해서 새롭게 재평가하게 되었습니다.
발단이 되는 것은 특정 사건이 아니라 최근 개발이 흘러가면서 자연스럽게 고민하다가 나온 것이죠.
우선 저도 당연히 One Page App 의 대열에 합류했습니다. 하나의 페이지에서 REST api를 이용하여 div에 각 서브화면을 열심히 그려주고 hash를 갱신하면서 상태를 대응하는 아름다운 세상!
하지만 결과적으로 리스트, 뷰 정도 두어개 있는 화면은 감당할만했지만 실서비스레벨로 복잡한 곳에 적용하기에는 다수의 문제가 일어났습니다.
- applicationDomain이 결국 document 한 개이므로 수 많은 인메모리 객체가 한 군데에 쌓여 있어 통제 불능에 빠지기 쉬워졌습니다.
- 하나의 html에서 점점 중첩되는 DOM객체에 압도되어 점점 느려지고 사용성이 나빠졌습니다.
- 결과적으로 다시 페이지를 분리하게 되었습니다.
다시 리스트의 얘기로 넘어가서 최근 대부분 모바일웹사이트에서 채용하고 있는 리스트는 하단으로 스크롤을 내리면 계속 불러오는 식의 리스트입니다.
적당히 스크롤을 내리다가 상세페이지로 들어갈 때 새 페이지로 들어갔다면, 돌아왔을때 복원할 방법이 필요합니다.
정확하게는 그때까지 로딩된 리스트와 스크롤위치로 복원할 방법이 필요한거죠. 여기에도 다양한 실험을 반복해봤습니다.
- 걍 쌩까고 리스트 처음부터 다시 시작하는 방식
- 욕 엄청 먹음
- 직전에 구성되었던 화면의 리스트를 부르고 스크롤을 위로 하면 위로도 채우로 아래로 하면 아래로도 채워가면서 만들어지는 방식
- 스크롤통제가 매우 난해해지고 사용성이 결과적으로 좋지 않음
- innerHTML과 scrollTop을 읽어들여 LocalStorage에 저장해두고 다시 복원하는 방식
그렇습니다. 결국 3번을 쓰게 되더군요(좋은 대안이 있으면 댓글로 ^^) 근데 3번을 사용하면 기존의 js작업 전체가 이를 불가능하게 한다는 사실을 발견했습니다.
기존의 리스트가 추가되는 과정을 기술하면 다음과 같습니다.
- RESTapi로 json을 불러온다.
- 파싱한 뒤 DOM템플릿과 결합하여 데이터를 대입하고
- 이벤트 등의 객체 작업을 끝마친 뒤
- 컨테이너에 삽입해준다.
너무나 당연해보이는 이 과정에서 2, 3번이 innerHTML로 복원하는 것을 불가능하게 하는 것입니다.
단순히 문자열로 되어있는 HTML을 복원하는 것만으로는 인메모리객체 작업을 끝낸 상태로 복원할 수 없는 것이죠.
이 문제를 해결하려다 갑자기 옛날 생각이 났습니다. 옛날이라 하면 서버에서 전부 html을 만들어서 토하는 스타일이죠. 그 당시 DOM level1 의 이벤트를 즐겨 사용하곤 했죠. 다음과 같은 코드라고 볼 수 있습니다.
<div onclick="action1()">action1</div>
이 코드가 갑자기 새롭게 보이는 것 입니다. 그전에는 DOM에 로직을 기술했다고 욕먹던 코드인데 이 HTML이라면 innerHTML로 복원해도 잘 작동할게 아닌가 싶은거죠.
물론 onclick같은걸 쓸 생각은 없지만 아이디어를 정리해보자면 간단히 말해
- 객체와 연결할 정보를 HTML에 문자열로 기술하여 저장하거나 복원시에 영향이 없게 한다
라는 것이죠. 대체할만한 후보가 HTML5의 data-*스펙입니다. 자유롭게 DOM에 추가적인 정보를 기술할 수 있게 해주니까요.
실행시점에 바인딩될 객체의 힌트를 주는 아이디어
해서 결과적으로 HTML을 작성할때 다음과 같은 정보를 기술하게 된다는 것입니다.
<div data-click="action1" data-data="data1"....></div>
이벤트리스너 뿐만 아니라 객체적으로 연결해야하는 모든 정보를 기술해버리는 것이죠.
그럼 그 키에 해당되는 순수한 js객체만 있다면 innerHTML로 위의 HTML을 넣어도 아무 문제가 없을 것입니다.
대표적인 실행시점 바인딩은 이벤트 위임입니다. 즉 이벤트를 저 div에 거는게 아니라 body에 걸어버리는 것이죠.
var listener = { action1:function(e){ console.log('action1'); } }; document.body.onclick = function(e){ var target = e.target, //현재 최상위에 있는 대상 type = e.type, //이벤트 타입 key = target.getAttribute('data-' + type); //대상의 data-click if(listener[key]) listener[key].call(e.target, e); //정의된 리스너가 있으면 바인딩한다 };
위의 흐름에서는 아예 div가 코드 상에 나오지도 않고 body의 리스너에서도 div의 data-click값을 읽어들였을 뿐 div에게는 아무것도 연결하지 않습니다.
즉 인메모리 객체에 대한 작업이 없는 것이죠.
이러한 흐름에서는 위에서 언급했던 2,3번의 객체연결작업이 완전히 없어서 자유롭게 html조각을 삽입하여 기존 상태를 복원할 수 있게 됩니다.
untiedJS
언타이드JS는 이러한 아이디어 바탕을 두고 오픈소스로 구현해가고 있는 프로젝트입니다.
이제 막 시작단계지만요. 관심있는 분들은 제게 연락주시면 커미터로 지정해드립니다.
https://github.com/untiedJS/untiedJS
저장소는 매우 부담없이 운영하고 있습니다. 딱히 풀리퀘나 버전관리를 신경쓰지 않고 웹에서 업데이트도 하고 자유롭게 웹에서 파일을 추가삭제하는 식으로 일단은 아이디어를 만들어가는데 주력하고 있는 정도입니다(깃헙이지만 마치 개발자를 위한 게시판처럼 쓰고 있는 상태입니다 ^^)
나중에 코드가 좀 정리되고 안정화되면 본격적으로 브랜치관리를 하겠지만 그 전까지는 방만하게 운영할 생각입니다.
recent comment