티스토리 뷰
0. Intro
이전 편을 쓴 지 일주일이 넘었네요.
빠르게 되짚고 시작해보도록 하겠습니다.
왜냐면 지금 이미 새벽 4시거든요.
그리고 오늘은 화요일이라, 오늘도 출근을 해야하니까요!
우선 저번 포스팅에서 했던 마지막 단락을 되짚어 보겠습니다.
index.html 이라는 하나의 정적 파일이 있고, 브라우저는 얘만 로딩합니다.
그래서 SPA(Single Page Application) 개념이 적용이 되는 거였구요.
그 index.html 파일에는 우리가 익히 아는 <div> 태그 하나와,
build.js를 정의하는 코드로 이루어져 있었습니다(기타 메타태그는 그냥 무시할게여)
그리고 딱 하나 있는 <div> 태그가 가진 id ‘app’를, Vue Application 인스턴스(Vue 2.x버전의 경우 Vue 인스턴스)가 가리킴으로써 그 위치에 Vue로 이루어진 화면들이 그려진다고 말씀을 드렸었습니다.
여기서 엔트리파일이라는 녀석이 등장하구요.
Vue Application 인스턴스의 생성 및 id가 app인 <div>태그를 가리키도록 정의해 놓은 파일을 우리는 엔트리파일(main.js)이라고 부릅니다. 또한, 엔트리 파일은 모듈로 정의할 수 있는 N개의 파일들이 build.js라는 하나의 파일로 집약될 수 있도록 돕는 진입점이라고 했습니다.
여기까지가 저번 포스팅의 내용이었죠.
자, 여기까지 잘 따라오셨으면 이제 본 포스팅을 시작하도록 하겠습니다. 이번 포스팅의 주제는 예고했던대로 Router에 대해 설명해보겠습니다.
1. Router(vue-router)
우리가 흔히 아는 라우터는 이겁니다.
사진의 출처를 따라 위키백과에서 '라우터'로 검색을 해보면 아래와 같이 정의되어 있구요
라우터(router, 혹은 라우팅기능을 갖는 공유기)는 패킷의 위치를 추출하여, 그 위치에 대한 최적의 경로를 지정하며, 이 경로를 따라 데이터 패킷을 다음 장치로 전향시키는 장치이다. 이때 최적의 경로는 일반적으로는 가장 빠르게 통신이 가능한 경로이므로, 이것이 최단 거리 일수도 있지만, 돌아가는 경로라도 고속의 전송로를 통하여 전달이 되는 경로가 될 수 있다. 간단히 말해 서로 다른 네트워크 간에 중계 역할을 해준다.
말이 되게 기네요. 쉽게 설명하자면 그냥 들어 온 요청을 적절한 곳으로 보내주는 겁니다. 이쪽으로 가세요, 저쪽으로 가세요, 하고 안내해주는 역할이라는거죠.
Vue에서의 Router 개념도 이와 마찬가지입니다.
다만, 위키에 있는 라우터의 정의와 비교를 해보자면 들어 온 요청은 path, 적절한 곳은 vue 파일, 에 대입이 됩니다. 이때 언급되는 path는 아래와 같이 URL 개념에서 나오게 되는데요.
노란색으로 강조된 path라고 정의되어 있는 영역이 보이시나요.
우리가 흔히 host라고 부르는 웹 브라우저의 주소에서 포트 이후의 뒷 영역을 path라고 정의합니다.
기존에 SPA가 아닌 형태로 웹개발을 많이 하셨던 분들이라면 굉장히 익숙한 형태일텐데요.
login.do로 넣으면 login.jsp가 불린다거나, main.jsp라고 넣으면 진짜 main.jsp가 호출된다거나.
그것과 거어어어어어어어어의 똑같다고 보시면 되는데, 쪼오오오오오오오끔 다릅니다.
기존에 SPA가 아닌 환경에서는 보통 path 영역에 파일명이나 혹은 컨버팅된 대체 경로를 주입함으로써 웹 브라우저에게 해당 웹 페이지를 불러오게끔 했습니다.
하지만 누누이 말씀드린 것처럼 SPA는 단 하나의 정적 페이지를 기준으로하기 때문에 URL의 path 영역값이 바뀐다고해서 다른 정적 파일을 부르지 않습니다(정확하게는 URL의 path가 바뀌면 새로운 정적 파일을 로딩해야 하지만, SPA는 오직 하나의 정적 파일밖에 없으니 아마도 404 Not Found가 뜰 겁니다. 말 그대로 호출 할 HTML 파일이 없으니까요)
하지만 우리는 로그인 기능을 가진 login.jsp도 홈 화면 역할을 할 main.jsp도, 도서를 대여하게 해주는 rent.jsp등등, 기존에 각 파일별로 나뉘어져 있던 기능을 가진 화면들이 여전히 필요합니다.
웹 서비스는 그런 화면과 기능들로 이루어져 있으니까요.
그래서 SPA기반의 Vue에서도 각각의 vue 파일들이 화면과 기능을 가지고 있습니다. 또한, URL에 적절한 path를 입력했을 때 어떤 vue 파일이 호출되는지에 대한 정의와 동작도 가지고 있습니다. 바로 router가 말이죠.
백문이 불여일견이라, 우리는 백마디 말보다 한 줄의 코드가 더 이해하기 쉬울겁니다.
그러니 바로 예시코드를 보시겠습니다.
import Home from '@page/home';
import Content from '@page/content';
import mainFrame from '@page/frame/mainFrame';
import Intro from '@page/intro';
import BackEnd from '@page/backEnd';
import FrontEnd from '@page/frontEnd';
import NotFound from '@page/notFound';
const router_main = [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/content', component: Content },
{ path: '/main', component: mainFrame,
children: [
{ path: '', redirect: '/main/intro'},
{ path: 'intro', component: Intro},
{ path: 'front', component: FrontEnd},
{ path: 'back', component: BackEnd},
]
},
{ path: '/:catchAll(.*)', component: NotFound },
]
위 코드는 제가 깃헙에 올려놓은 vue_starter 프로젝트의 router_main.js 파일입니다.
(경로는 src/router/router_main.js입니다)
라우터에 대한 가타부타 설명이 없었어도 위 코드의 동작방식을 충분히 어림짐작하실거라고 생각합니다.
하지만 그래도 굳이 설명을 해드려야, 이 포스팅을 작성하는 의미가 있겠죠?
찬찬히 짚어봅시다.
path 가 “/“일 때는 “/home” 경로로 리다이렉트합니다.
즉, vue_starter 프로젝트를 받으셔서 npm run serve를 하신 분이라면 http://localhost:9090/ 이 경로로 접근시 http://localhost:9090/#/home 경로로 자동 전환 될 겁니다. 그리고 “/home”이라는 path로 접근시 Home 이라는 컴포넌트에 매핑되도록 정의되어있습니다.
이때 매핑된 Home은 맨 첫줄에 작성된 import Home from ‘@page/home’ 입니다.
여기서 경로를 정의한 '@page/home'에서 @page로 되어있는 부분은 alias입니다. 실제 가리키는 경로는 vue_starter프로젝트의 src\page 디렉토리이며 이와 관련된 정의는 vue.config.js 파일에 되어있습니다.
잠깐 논외로 해당 코드를 짤막하게 나마 설명해보자면 import 라는 문법은 ES6에서 본격적으로 등장한 기법으로, 각각의 모듈들을 가져와서 활용할 때에 쓰입니다. 마치, JAVA에서 여러 라이브러리를 가져다 쓰듯이 말이죠. 다만, 여기서 import는 '모듈'을 가져오는 것이라고 했는데, 실제 코드에서는 아래와 같이 component에 가져온 모듈을 매핑해서 쓰고 있습니다.
{ path: "/home", component: Home },
그리고 실제 모듈이라고 가져왔지만, 해당 파일은 엄연히 home.vue 라는 Vue 파일이죠. 그렇기에, 이는 모듈임과 동시에 컴포넌트이기도한데요. 사실 모듈 & 컴포넌트...이 둘은 비슷하면서도 다른 개념입니다. 이에 대해 상세히 설명하기엔 본 포스팅의 주제에서 너무 엇나가기 때문에 그냥 여러분이 여기서 알아야 할 개념은 Vue 파일들을 하나하나가 컴포넌트다, 라고 이해해주세요.
(컴포넌트 임과 동시에 모듈이고 모듈임과 동시에 컴포넌트이기 때문에, 앞선 포스팅에서부터 계속해서 말씀드리고 있던...모듈들이 모여서 webpack이라는 모듈 번들러에 의해 하나의 build.js로 정착된다, 라는 개념에 vue 파일들도 속하게 되는 것입니다)
그래서 각각의 vue 파일은 컴포넌트이기 때문에 위와 같이 "/home" 이라는 path로 접근하게되면 home.vue 파일이 화면에 그려지게 되는 겁니다. 마찬가지로 “/content” path로 접근시 src/page 경로에 있는 content.vue 파일이 컴포넌트로써 화면에 그려지게 됩니다.
맨 마지막에 적힌 catchAll은 Vue 3.x대에 도입된 문법으로 router에 의해 정의되지 않는 기타 path 값들을 잡아냅니다. 즉, 미정의한 경로에 대해 보여줄 vue 컴포넌트를 지정할 때 쓰이는 거죠. 이와 비슷한 맥락으로 Vue 2.x 대에서는 “/*” 라는 path에 미정의한 경로로 접근시 보여질 vue 컴포넌트를 매핑시켜 놓곤 합니다.
2. 엔트리파일과 App.vue
여기까지 보셨으면 이제 Router에 대해서 아셔야 할 게 한가지가 남았습니다.
되짚어보자면 브라우저에 주소를 입력하거나 링크를 통해 우리의 웹 서비스로 접근을 했다면,
URL의 path가 변경될때마다, 각각에 알맞게 매핑된 Vue 컴포넌트들이 호출된다.
그리고 그 vue 컴포넌트(파일)들이 화면에 그려지게 된다, 라는 겁니다.
바로 이게 이번 포스팅이 핵심개념이구요.
이제는 이 개념이 실제로는 어떻게 파일들이 연계되어 호출되는지를 설명하겠습니다.
여기에서 말하는 ‘연계’란, 그렇게 경로(path)와 컴포넌트가 1:1로 매핑된 vue 컴포넌트들이 ‘어디’에 '어떻게' 그려지는 지에 대한 설명을 뜻합니다.
결론부터 말씀드리자면, <router-view>라는 태그가 있는 위치에 그려지게 됩니다.
제가 제공해드리는 vue_starter를 기준으로 보자면 App.vue 파일을 열어보시면 아래와 같이 <template>영역에 해당 태그가 있는 것을 확인하실 수 있구요
<template>
<div>
<router-view/>
</div>
</template>
바로 저 위치에 router_main.js에 정의된 root path인 “/“부터 매핑되어 화면에 렌더링되게 됩니다.
그럼 여기서 의문점이 하나 드는데요.
<router-view>가 여기저기 모든 vue 파일에 선언되어 있으면 어떻게 path가 매핑이 되게 되는걸가요?
가령, App.vue에도 있고, home.vue에도 있고 content.vue에도 있고 막 그러면 어떻게 되는 건가요?
아무데나 <router-view>가 있으면, Vue는 어떻게 그 위치가 그 URL에 맞춰서 렌더링을 하는지 판단하는 건가요?
이 질문에 대한 답은, Vue는 ‘트리’형으로 화면이 구성되는 걸 기본으로 하기 때문에, vue 컴포넌트들에 매핑되는 URL 체계도 ‘트리’형을 이루게 됩니다.
즉, vue 파일들의 기점은 App.vue를 루트(Root)로하여 전체 Router가 형성된다는 것입니다.
여기서 한 발자국 더 나가면 화면의 구성자체가 트리이기 때문에, 화면을 이루는 vue 컴포넌트들과 1:1로 매핑된 URL 경로(path) 또한 트리형태로 이루어져있다는 것입니다. 그리고 트리이기 때문에 각각의 경로는 Children을 가질 수 있습니다.
아까 보여드렸던, router_main.js 코드를 기억하시나요?
안 나실 걸 당연히 알기때문에 한 번 더 보여드릴게여.
const router_main = [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/content', component: Content },
{ path: '/main', component: mainFrame,
children: [
{ path: '', redirect: '/main/intro'},
{ path: 'intro', component: Intro},
{ path: 'front', component: FrontEnd},
{ path: 'back', component: BackEnd},
]
},
{ path: '/:catchAll(.*)', component: NotFound },
]
자, "/main" 이라는 경로에 정의된 사항들을 집중해주세요.
타 경로들은 redirect, component 만을 가지고 있는 반면에, "/main"은 children 이라는 배열 형태의 값까지 정의를 해주고 있습니다. 그리고 그 아래에는 마치, 누가 재귀문이라도 돌린 것처럼 path와 redirect, component가 추가 정의되어 있습니다.
어때요, ‘아 저렇게 쓰는거구나?’하고 감이 잡히시나요?
이해를 돕기 위해 위 내용을 그림으로 또 한 번 보도록하죠.
위 구조는 http://localhost:9090/#/main 경로로 접근했을 때를 보여주고 있습니다.
vue의 트리구조로 봤을 때 루트를 담당하는 최외곽에는 App.vue가 존재하구요.
App.vue는 <template> 영역에 아래와 같이 <router-view>를 가지고 있기때문에,
<template>
<div>
<router-view/>
</div>
</template>
경로에 따른 router구성에서도 트리구조의 최상단인 루트를 담당하게 됩니다.
즉, 현재의 router_main.js에 정의된 기준으로 보자면 아래 스크린샷과 같이 빨간 블럭내에 있는
“/“, “/home”, “/content”, “/main” 의 경로(path)들이 App.vue의 <router-view>에 매핑되어 있다고 보시면 되겠습니다.
그리고 /main 경로를 보면 children을 추가로 정의하였는데, 이는 /main 이라는 경로 뒤에 각각 intro, front, back 이라는 값들이 추가되었을 때 렌더링되는 화면(vue 컴포넌트)들을 정의한 것입니다. 이 화면들이 그려지는 위치는 children으로 정의된 mainFrame.vue 파일내의 <router-view>인데요.
<template>
<div class="frame">
<gnb-frame></gnb-frame>
<router-view class="main-router"/>
</div>
</template>
vue_starter 프로젝트를 받고 npm run serve 명령어를 통해 실행하셨다면, http://localhost:9090/#/main 이 주소로 접속해봐주세요.
정상적으로 실행을 했다면 아래와 같은 화면을 마주하게 되실겁니다.
여기서 빨간색 영역이 gnbFrame.vue이고 아래쪽 녹색 테두리 영역이 2레벨째의 <router-view>이며,
경로에따라 해당 위치에 렌더링되는 파일들은 router_main.js를 보면 다음과 같습니다.
http://localhost:9090/#/main/intro 경로라면 intro.vue
http://localhost:9090/#/main/front 경로라면 frontEnd.vue
http://localhost:9090/#/main/back 경로라면 backEnd.vue
정말 그런지 확인을 위해 상단의 메뉴를 눌러보시면,
브라우저의 URL과 아래쪽 영역이 바뀌는 것을 확인할 수 있을 겁니다.
3. 정리!!
자, 그럼 늘 그래왔듯이, 다시 처음 index.html부터 짚어들어가 보겠습니다.
1.. SPA환경이므로 정적 파일인 HTML은 index.html 하나만 있습니다.
2. index.html은 <div id=“app”/> DOM 태그를 가지고 있구요.
3. build.js를 포함하고 있습니다.
4. build.js는 엔트리 파일(main.js)을 기점으로 생성된 Vue Application 인스턴스를 가지고 있으며
5. Vue Application 인스턴스는 createApp function의 파라미터로 App.vue를 지정하였고
6. App.vue는 <router-view> 태그를 가지고 있는데, 해당 위치가 바로 URL의 path에 따라 매핑된 vue 컴포넌트들이 렌더링되는 위치입니다.
이렇게 정리가 되겠습니다.
앞선 포스팅에서 말했던 것들에, 마지막 6번 항목이 추가가 된 것이죠.
뭐, 물론 본 포스팅의 예시에서나 실제 프로젝트에서는 좀 더 복잡하게 Router를 정의해서 쓰시겠지만,
그 이상부터는 각자 정의하고 풀어나가셔야 할 부분이니까요.
추가로 조금이나마 도움이 될까 싶어서, vue_starter 레파지토리에 GNB를 활용하는 코드까지 추가를 해두었으니 참고해주시면 될 것 같습니다(이와 관련된 짧은 설명을 아래 별첨에 작성해둡니다)
돌아보니 짧은 포스팅 같은데 쓰다보니 제법 길었던 것 같은 느낌입니다.
다음 포스팅은 (이제서야) 본격적으로 Vue 파일내부부터 차근히 설명해보도록 하겠습니다.
궁금하신 사항은 언제든지 댓글 달아주시구요, 감사합니다.
-1. GNB 형태로 활용하기
vue_starter 레파지토리를 기준으로 우선,
GNB를 활용하는 예시 경로는 http://localhost:9090/#/main 로 정의를 해두었습니다.
위에서도 보셨겠지만 해당경로로 들어가보시면 아래와 같은 형태로 브라우저에 렌더링이 될 거구요.
이에 관여하는 파일은 총 N개이며 이들에 대해 차근히 설명해 보도록 하겠습니다.
1. router_main.js
위에서도 몇번이나 언급된 파일입니다. Router의 핵심이라고 할 수 있으며 각 경로(path)에 대해 렌더링 될 vue 컴포넌트들을 1:1로 매핑 정의하고 있습니다. GNB 예제를 중점적으로 보자면 /main 을 시작점으로 봐주시면 됩니다.
2. App.vue
Router 구성에서 루트 역할을 맡은 <router-view>를 정의하고 있는 파일이며, Vue Application 인스턴스에서도 모든 vue 파일들의 기점이 되는 루트 역할을 합니다. 실제 "/"경로로 접근시 렌더링되는 화면입니다.
3. mainFrame.vue
GNB로 구성되는 화면의 전체 틀을 잡아줍니다. 그런 의미에서 mainFrame 이라는 이름을 붙였구요. 해당 파일에는 흔히 헤더라고 불리는 GNB역할의 gnbFrame.vue 가 정의되어 있으며 App.vue에 이어서 2레벨째의 <router-view> 또한 정의되어 있습니다.
4. gnbFrame.vue
mainFrame.vue의 상단에 그려지는 GNB(헤더)영역만을 별도로 정의한 컴포넌트입니다. 현재는 아래 코드와 같이 Intro, Front-end, Back-end 라는 이름의 3개 메뉴를 가지고 있으며
data(){
return{
gnbMenu: [
{name: 'Intro', url: '/main/intro'},
{name: 'Front-end', url: '/main/front'},
{name: 'Back-end', url: '/main/back'},
]
}
}
각각의 메뉴는 화면상에 보일 이름(name)과 메뉴를 클릭했을 때 이동할 경로(url)를 가지고 있습니다.
메뉴를 클릭했을 때 Router가 인식하는 범위 내에서 URL을 변경하는 로직을 아래와 같이 가지고 있으며
moveTo(url){
this.$router.push(url);
}
위 코드는 파라미터로 전달된 url로 Router를 이동시키는 코드이고, 여기서 전달되는 url은 gnbMenu 라는 배열형 변수가 내포하고 있는 각 url 입니다(어떻게 클릭해서 저 moveTo라는 메서드가 호출되는지등에 대한 건 다음 포스팅에서 다룹니다)
5. intro.vue, frontEnd.vue, backEnd.vue
mainFrame.vue에서 사용된 gnbFrame.vue의 메뉴를 클릭했을 때, Router가 이동(여기서 말하는 이동은 url의 변경에 따라서 <router-view> 영역에 알맞은 vue 컴포넌트가 그려지는 것을 의미합니다)하게 되고 그에 따라 그려지는 각각의 vue 컴포넌트들입니다.
현재는 그저 <h3>태그를 이용하여 간단한 문자열을 출력하는 정도지만, 추후 여기에 각각 필요한 로직들을 넣으시면 됩니다.
'개발 > 프론트엔드(Front-end)' 카테고리의 다른 글
[Vue.js_#-1] Vue.js 가이드 Github page를 완성했습니다. (0) | 2021.04.06 |
---|---|
[Vue.js_#04_2] 라우터(vue-router) vol.2 (1) | 2020.11.12 |
[Vue.js_#03] N : 1 (feat. webpack) (2) | 2020.10.05 |
[Vue3] Font Awesome 적용하기 (0) | 2020.09.30 |
[Vue.js_#02] SPA (1) | 2020.09.29 |