SPA와 SPA 라우팅 원리
1.
기존의 웹 서비스(서버사이드 랜더링)
•
특징
•
장점과 단점
2.
Ajax 로 특정 부분만 새로 그리는 웹 서비스
•
특징
•
장점과 단점
3.
SPA 방식
•
특징
•
장점과 단점
•
실제 프레임워크들에서 사용하는 SPA의 원리
1. 기존의 웹 서비스 (서버사이드 랜더링)
"서버에서 html을 완성해서 보내주는 방식"
기존의 웹 서비스는 앵커에 명시되어있는 자원(html)을 서버에 요청하고, 응답으로 받은 내용을 브라우저에 표현한다.
코드로 보기
장점과 단점
장점
•
브라우저가 응답을 받자마자 랜더링을 할 수 있어서 빠르다
•
자바스크립트 코드가 없어서 빠르게 작성할 수 있다.
단점
•
중복되는 데이터가 계속 네트워크를 타고 들어온다.
◦
위 코드의 <nav></nav> 영역은 동일한 부분임에도 불구하고 계속 네트워크로 넘어간다.
2. Ajax 로 특정 부분만 새로 그리는 웹 서비스
"서버에서 첫화면을 그리고 다음부터는 ajax로 데이터를 가져온 후에 클라이언트에서 html을 랜더링 하는 방식"
코드로 보기
코드를 부분부분씩 나누어서 살펴보자.
1.
각각의 앵커를 click 시에 이벤트 부여하기
우선 index.html 부분을 보면, <a> 에 addEventListener를 통해 click 시에 콜백함수를 실행하도록 했다.
<li><a href="/">main</a></li>
<li><a id="navSub1" href="">sub1</a></li>
<li><a id="navSub2" href="">sub2</a></li>
HTML
복사
→ a 태그에 id인 'navSub1' 'navSub2'를 이용해서 이벤트를 부여했다.
document.getElementById('navSub1').addEventListener('click', function(e) {
e.preventDefault();
drawSection('/sub1.json');
});
document.getElementById('navSub2').addEventListener('click', function(e) {
e.preventDefault();
drawSection('/sub2.json');
});
JavaScript
복사
2.
drawSection 함수
drawSection 함수에서는 ajaxGet 함수를 호출하는데,
첫번째 인자는 '/sub1.json' 또는 '/sub2.json'을 전달하는 url
두번째 인자는 익명함수이며, 해당 함수는 callback이라는 이름의 함수로 ajaxGet에 전달됨.
function drawSection(url) {
ajaxGet(url, function(response) {
var data = JSON.parse(response);
document.querySelector('section').innerHTML = '<h1>' + data.title + '</h1>' +data.content
});
}
JavaScript
복사
→ json 값은 아직 서버에 있다. 그러므로 그저 위치만 넣는다고 전달되지 않는다.
Q. 왜 json 값을 넣을때 서버가 필요한가?
→ 이것에 대한 답변은 웹페이지를 구성하는 과정을 생각해보면 알 수 있다. 우리는 연습중이기 때문에 같은 디렉터리에 있는 문서를 받아오면 된다고 생각할 수 있다. 그러나 실제로 웹페이지는 웹 브라우저가 필요한 리소스들을 요청하고 전송을 받아서 구성된다. 따라서 json이라는 새로운 리소스 역시도 요청과 전송의 과정이 필요하다. 그러므로 이것을 해주는 서버가 필요하다.
'ajaxGet'함수를 통해서 들어올 수 있으므로 현재 상태에서는 아직 response에 들어온 값은 없다.
3.
ajaxGet 함수
ajaxGet 함수에서는 전달받아온 url과 callback 함수를 가지고, XMLHttpRequest 라는 객체를 생성한다.
•
open메서드를 통해서 http 방식과 얻어올 리소스를 지정해준다.
•
http 헤더는 원래 웹 브라우저가 자동으로 설정해서 보내므로, 사용자가 직접 설정할 수 없었음.
Ajax를 사용하여 HTTP 요청 헤더를 직접 설정하거나 응답 헤더의 내용을 직접 확인할 수 있게 됨.
◦
xhr.setRequestHeader(헤더이름, 헤더값)
헤더 이름 : HTTP 요청헤더에 포함하고자 하는 헤더의 이름
헤더 값 : 헤더의 값
•
따라서 이경우에만 인자로 받았던 callback 함수를 실행하도록 한다.
•
apply 라는 함수의 기본 메소드는 함수를 호출하면서 특정 인자를 추가로 전달하고 싶을때 사용한다.
function. apply(this로 사용될 것, 인자들을 배열로 묶기)
위와 같은 형식으로 사용되므로, 이 경우 callback 함수에 xhr.response를 배열로 묶어서 전달하고 ajaxGet의 this를 callback함수에서 this로 사용하도록 전달해준다.
function ajaxGet(url, callback) {
url = url || '';
callback = callback || function () { ; };
var xhr = new XMLHttpRequest(); //새로운 객체를 만들어줌.
xhr.open("get", url); //open 메서드 , request 방식, 얻어올 리소스
//http 서버에서
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
var that = this;
xhr.onload = function () {
callback.apply(that, [xhr.response]);
};
xhr.send(null);
}
JavaScript
복사
장점과 단점
장점
1.
필요한 부분만 새로 그리기 때문에 낭비가 없다.
2.
기존의 링크를 타고 다니던 웹 서비스보다 편한 사용자 경험을 준다(아무래도 리소스 다운시 오래 걸렸겠지..기존의 방식은)
단점
1.
히스토리 관리가 안된다. → 뒤로가기, 앞으로 가기시 브라우저는 sub1, sub2 앵커를 눌렀을 때의 상태는 전혀 없다는 듯이 행동.
2.
sub1 앵커를 눌렀을 때 어떤 액션을 하는지 추적하기 어려움.
예전에는 href에 주소가 있어서 알 수있었지만 지금은 빈문자열→ 이벤트 리스너를 타고 가서 봐야함.
3.
JS코드가 길다.
3. SPA
location.hash(#로 시작하는 문자열)와 HTML5의 history API를 통해서 논리적으로 페이지를 분리하면서도 이동이 가능하게 하는 기술
전체 코드로 보기
코드 이해하기
순서대로 코드의 흐름을 읽어보자.
1.
DomContentLoaded 되거나 hashchange 된 시점에 router가 실행됨.
2.
router 함수
url에서 #이후에 나오는 식별자를 value로 하는 Domstring 을 활용하여 (location.hash를 사용)
•
routerMap[hashValue]를 찾을 경우 → 해당 함수를 바로 실행.
•
routerMap[hashValue]를 못찾을 경우 → otherwise 함수를 실행.
3.a routerMap[hashValue]을 찾은 경우
•
빈 hash 라면 메인 화면
•
‘sub1’또는 ‘sub2’라면 화면을 그려주는 drawSection()을 실행
3.b routerMap[hashValue]를 못찾은 경우 → otherwise 함수 실행
•
'Not Found'; 노출
4.
drawSection()과 ajaxGet()으로 필요한 부분만 요청해서 받기.
ajax를 사용한 경우와 달라진 점
앞서 중복된 내용은 생략하고 달라진 부분은 크게 두가지 있다.
1.
특히 DomContentLoaded 이벤트가 이루어지는 시점에 router 함수를 실행시키게 한것은!
→ 이것은 해시값을 가지고 있는 url을 브라우저에서 요청할 경우에 라우팅이 이루어지도록 하기 위해서이다.
2.
앵커에도 해시값(#sub1, #sub2)이 들어가서 명시적으로 확인이 가능하다.
장점과 단점
장점
•
실제로 url의 변화가 있기 때문에 히스토리 관리가 된다는 점
•
논리적으로 페이지가 분리될 수 있다는 점이 있다.
단점
•
확실히 js 코드가 많아졌다는 부분이다.
실제 프레임워크에서 사용하는 SPA, 그 원리
먼저 짚고 넘어가야 하는 부분이 있다. 이에 대한 개념은 차후에 다시 정리하려고 한다.
우선 간단하게 url의 구성에 대해서만 알아보면 url은 다음과 같은 구성을 가진다.
url의 구성
순서대로 잘라서 보자.
•
URI Scheme : http
•
사용자 정보 : reimaginer:password
•
hostname : www.tistory.com
•
port : 8000
•
path : /search
•
query parameter (=쿼리 문자열): q=test&debug=true
•
fragment identifier : n10 --> # 앞의 문자열로 표현한 URI가 가리키는 리소스 내부에서 더 세세한 부분을 특정할 때 이용
hash는 값이 변경되어도 서버에서 페이지가 갱신되지 않는다.
여기서 주목할 부분은 fragment identifier인 #n10 부분이다. 이와 같은 hash 값이 변경되어도 서버에서 페이지가 갱신되지 않는다.
hash 값은 문서에서 부차적인 자원의 참조를 목적으로 만들어졌기 때문에 실제로 서버의 페이지 갱신은 이루어지지 않는다.(실제로 여기서도 n10이라는 영역을 의미한다고 봐도 된다)
그치만, hash는 history에는 쌓인다.
그런데 유용하게도 hash를 변경하면 history에 쌓인다.
url이 변경되었기 때문이다.
url이 변경되면 히스토리는 쌓이게 되고, 뒤로가기 혹은 앞으로 가기를 누르게 되면 해시가 변경되기 전 또는 변경되었던 이후의 상태로 이동할 수 있다.
hashchange 이벤트 리스너
앞서 우리는 hash가 변경되는 시점에 router 함수를 실행하도록 ‘hashchange’이벤트 리스너를 달아주었다.
(참고 : history entry가 변경되는 경우에 실행되도록 popstate 이벤트 리스너를 달수도 있다.)
→ hashchange는 hash가 변경되었을때 실행되는 이벤트이고, 해시 부분이 변경될 때 라우터부분이 실행되도록 해서 실제로 url이 변경되면 다른 화면으로 라우팅이 이루어지는 효과를 줄 수 있었다.
이 글은 https://reimaginer.tistory.com/entry/spa-and-spa-routing#참고---if-else가-아닌-map이라는-자료구조를-사용한-이유 포스팅을 읽고 정리한 작성글입니다.