-
[Web] SPA(Single Page Application)Programming/Web 2020. 4. 2. 16:26
기존의 웹 서비스
기존의 웹 서비스는 링크(앵커 <a href="src">)를 클릭하면 해당 페이지로 이동하게 된다.
앵커에 명시되어있는 자원(일반적으로 html)을 서버에 요청하고 응답으로 받으 내용을 브라우저에 표현하게 된다.
이런식으로 매 페이지 마다 서버에 문서를 요청하고 응답받아서 표현한다.
123456789101112131415161718192021222324<!-- index.html --><!DOCTYPE html><html><head><meta charset="utf-8"><title>MAIN</title></head><body><nav><ul><li><a href="/">main</a></li><li><a href="sub1.html">sub1</a></li><li><a href="sub2.html">sub2</a></li></ul></nav><section><h1>MAIN</h1>This is main page.</section></body></html>cs 1234567891011121314151617181920212223<!-- sub1.html --><!DOCTYPE html><html><head><meta charset="utf-8"><title>SUB1</title></head><body><nav><ul><li><a href="/">main</a></li><li><a href="sub1.html">sub1</a></li><li><a href="sub2.html">sub2</a></li></ul></nav><section><h1>SUB1</h1>This is sub 1 page.</section></body></html>cs 장점
첫째, 브라우저가 응답을 받자마자 렌더링을 할 수 있어서 빠르다.
둘째, JavaScript 코드가 없어서 쉽게 작성할 수 있다.
단점
중복되는 데이터가 계속 네크워크를 타고 넘어온다는 것이다.
예를들어, 목차는 모든 페이지에서 동일하게 보여지는 부분인데 이 코드가 매번 서버로 요청할 떄 마다 응답으로 넘어가 낭비가 될 수 있다.
Ajax로 특정 부분만 새로 그리는 웹 서비스
기존 웹 서비스의 단점을 보완하기 위해서 서버에 첫 화면을 그리고 다음부터는 Ajax 방식으로 데이터만 가져온 뒤에 클라이언트에서 html을 렌더링하는 작업을 많이 했다.
즉 필요한 부분만 다시 그리도록 JavaScript 코드를 작성했다.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758<!DOCTYPE html><html><head><meta charset="utf-8"><title>MAIN</title></head><body><nav><ul><li><a href="/">main</a></li><li><a id="navSub1" href="">sub1</a></li><li><a id="navSub2" href="">sub2</a></li></ul></nav><section><h1>MAIN</h1>This is main page.</section><script>(function(){document.getElementById('navSub1').addEventListener('click', function(e) {e.preventDefault();drawSection('/sub1.json');});document.getElementById('navSub2').addEventListener('click', function(e) {e.preventDefault();drawSection('/sub2.json');});function drawSection(url) {ajaxGet(url, function(response) {var data = JSON.parse(response);document.querySelector('section').innerHTML ='<h1>' + data.title + '</h1>'+data.content});}function ajaxGet(url, callback) {url = url || '';callback = callback || function () { ; };var xhr = new XMLHttpRequest();xhr.open("get", url, true);xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');var that = this;xhr.onload = function () {callback.apply(that, [xhr.response]);};xhr.send(null);}})();</script></body></html>cs 코드가 굉장히 복잡해졌다. 각 앵커에 이벤트 리스너를 붙였다. 각 앵커를 클릭하게 되면 데이터를 서버에 요청해서 받은 응답으로 html을 새로 만들고 section의 innerHTML로 붙이는 코드이다.
장점
첫째, 필요한 부분만 새로 그리기 때문에 낭비가 없다.
둘째, 이 방법으로 기존의 링크를 타고 다니던 웹 서비스 보다 편한 사용자 경험을 줄 수 있다.
단점
첫째, 히스토리 관리가 안된다. 뒤로가거나 앞으로 갈 떄, 브라우저는 sub1, sub2 앵커를 눌렀을 때의 상태는 전혀 없다는 듯이 행동한다.
둘째, sub1 앵커를 눌렀을 떄 어떤 액션을 하는지 추적하기가 어렵다. 예전에는 href에 가야할 주소가 있었는데 지금은 그냥 빈 문자열만 있다. 저 먼곳에 이벤트 리스너에 어떤 액션을 하는지 적어두었다.
셋째, JavaScript 코드가 너무 많아졌다.
이 정도만 해도 page가 하나이기 때문에 SPA라고 할 수 있지만 현재의 SPA는 한 걸음 더 나아간다.
SPA (Single Page Application)
위키피디아에서 SPA의 정의를 찾아보면 아래와 같은 내용이 있다.
The page does not reload at any point in the process, nor does control transfer to another page, although the location hash or the HTML5 History API can be used to provide the perception and navigability of separate logical pages in the application.
locarion.hash와 HTML5의 history API를 통해서 예전 웹 페이지처럼 논리적으로 페이지를 분리하고 그 분리된 페이지를 이동하는 것이 가능하다는 뜻이다. hash는 URI에서 #으로 시작하는 문자열을 의미하는데 정확히는 Frament Identifier라고 한다.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162<!DOCTYPE html><html><head><meta charset="utf-8"><title>MAIN</title></head><body><nav><ul><li><a href="#">main</a></li><li><a href="#sub1">sub1</a></li><li><a href="#sub2">sub2</a></li></ul></nav><section><h1>MAIN</h1>This is main page.</section><script>(function(){var sectionEl = document.querySelector('section');var mainHtml = sectionEl.innerHTML;var routerMap = {'' : function() {sectionEl.innerHTML = mainHtml;},'sub1' : function() {drawSection('sub1.json')},'sub2' : function() {drawSection('sub2.json')}}function otherwise() {sectionEl.innerHTML ='Not Found';}function router() {var hashValue = location.hash.replace('#', '');(routerMap[hashValue] || otherwise)();}function drawSection(url) {//02와 동일하므로 생략}function ajaxGet(url, callback) {//02와 동일하므로 생략}window.addEventListener('DOMContentLoaded', router);window.addEventListener('hashchange', router);})();</script></body></html>출처: https://reimaginer.tistory.com/entry/spa-and-spa-routing [Reimaginer]cs 훨씬 더 스크립트가 많아졌다. 중요한 차이는 해시값이 변경되는 시점(on hashchange)에 해시값에 매핑된 함수를 실행하도록 해서 라우팅이 되도록 한 것이다.
또한 앵커에는 해시값을 넣어 명시적으로 확인할 수 있도록 했다. 이렇게 코드를 작성함으로써 논리적으로 페이지가 분리되고 히스토리도 관리가 된다.
장점
첫째, 히스토리 관리가 된다.
둘째, 해시값으로 페이지를 논리적으로 분리할 수 있다.
+)
이는 DOMContentLoaded 이벤트가 발생하는 시점에도 router 함수를 실행하는 이유와도 연결된다.
해시값을 가지고 있는 URL을 브라우저에서 요청할 경우에도 라우팅이 되도록 하기 위함이다. 페이지의 DOM Content loaded 시점에 라우팅 로직을 실행하여 해당 해시값에 맞는 페이지를 클라이언트 사이드에서 렌더링 하게 된다.
기능의 일관성을 지키기 위함이다.
단점
첫째, JavaScript 코드가 더 많아졌다. 그러나 역할별로 분리한다면 크게 관리가 어렵지 않다.
둘째, 검색 엔진 최적화에 어려움이 있다.
* 검색 엔진 최적화 (SEO, Search Engine Optimization)는 웹 페이지 검색엔진이 자료를 수집하고 순위를 매기는 방식에 맞게 웹 페이지를 구성해서 검색 결과의 상위에 나올 수 있도록 하는 작업
SPA의 라우팅 원리
hash는 변경해도 서버에 페이지가 갱신되지 않는다
브라우저에서 URI의 호스트명, path, query parameter를 변경하게 되면 해당 주소로 서버에 요청하고 응답을 받아서 화면이 갱신된다.
그러나 fragment Identifier 즉 hash는 변경되어도 화면을 갱신되지 않는다. 왜냐하면 hash는 문서에서 부차적인 자원에 대한 참조를 가리키기 위해서 만들었기 때문이다. 예를들어 http://tools.ietf.org/html/rfc3986#section-3.5 에서 #section-3.5는 rfc3986이라는 문서에서 section-3.5를 가리킨다. 문서 내의 참조이기 때문에 화면이 갱신될 필요가 없는 것이다.
이 규칙을 이용해서 SPA에는 hash를 이용하여 라우팅을 한다.
hash를 변경하면 history에 쌓인다.
hash를 변경하여도 서버에 새로 요청을 보내서 페이지를 갱신하거나 하지는 않지만 history는 쌓인다. (URI가 변경되면 히스토리가 쌓인다.
쉽게 설명하면 뒤로가기를 누르게 되면 해시를 변경하지 않았던 상태로 이동하고 다시 앞으로 가기를 누르게 되면 해시를 변경했던 상태로 이동할 수 있게 되는 것이다.
이를 이용라여 SPA에서 히스토리를 관리할 수 있게 된다.
hashchange & popstate event
브라우저에서 hash가 변경될 때 마다 hashchange & popstate 이벤트가 발생한다.
hashchange는 말 그대로 hash가 변경되었다는 의미의 이벤트이고,
popstate는 history entry가 변경되었을 경우에 발생하는 이벤트이다. (??)
angular, react, vue 등을 사용하면 편리하게 복잡한 코드 없이 SPA를 작성할 수 있다.
정리
SPA는 index.html 파일 하나에서 js, css 등 리소스 파일들과 모듈을 로드해서 페이지 이동없이 특정 영역만 새로 모듈을 호출하고 데이터를 바인딩하는 개념이다.
기존의 웹 서비스, Ajax, SPA 발전 상황에 따라 다른 방식이라고 설명했지만, 사실 실제 서비스에서는 이 세 가지를 상황에 맞게 섞어서 웹 서비스를 만든다.
자료 출처
- http://devstory.ibksplatform.com/2017/08/spasigle-page-applications.html
- https://linked2ev.github.io/devlog/2018/08/01/WEB-What-is-SPA/
- https://reimaginer.tistory.com/entry/spa-and-spa-routing
'Programming > Web' 카테고리의 다른 글
[Spring Boot] spring jar 파일 생성 (0) 2021.03.26 [JavaScript] call, apply, bind (0) 2020.04.01 [Web] Ajax 시작하기 (0) 2020.03.23 [Web] Accept와 Content-type (0) 2020.03.18 [Web] CGI, Servlet, JSP (0) 2020.03.06