티스토리 뷰

어,음…저번 포스팅때 이번편부터 본격적으로 Vue 파일들에 대해 파헤쳐보겠다고 했던 거 같은데,

생각해보니 라우터와 관련해서 못 다한 이야기가 있어서 이렇게 또 라우터를 주제로 작성하게 됐습니다.

 

그냥 나중에 좀 더 상세한 내용을 담는 포스팅에서 쓸까하다가, 그냥 나온김에 쓰자 싶어서요.

(제 성격이 방학숙제조차도 미뤄놓으면 되게 찜찜해하는 타입이라 그렇습니다)

 

그래서 이번편은 #04_2편 입니다. 


1. vue-router는 사실 2개야.

우선, Vue에서 라우터 기능을 사용하기 위해서 우리는 하나의 npm 패키지를 설치해줘야 합니다.

대략 vue-router 라는 이름을 가진 npm 패키지구요, Vue.js를 활용하신다면 필수적으로 사용할 수 밖에 없는 라이브러리입니다.

 

근데 여기서 중요한 점은 포스팅을 작성하고 있는 현 시점에 vue-router의 종류가 2가지라는 겁니다.

 

하나면 하나지, 둘이겠느냐. 둘이면 둘이지 셋은 아니야

 

같은 npm 패키지인데 왜 2개일까요.

바로 하나는 Vue 2.x대를 위한 vue-router 3.x 버전이구요.

나머지 하나는 가장 최근에 나온 Vue 3.x대를 위한 vue-router 4.x버전(a.k.a vue-router-next)입니다.

 

참고로 vue-router-next는 놀랍게도 현재 베타버전입니다.

 

언제 정식 릴리즈 해 줄 건가요...ㅠㅠㅠ

 

뭐, 어쩔 수 없겠죠. Vue 3.0 이 9월 중순에 공식 릴리즈를 했으니, 그에 대응하는 오픈소스들은 변경된 사항도 반영해야하고 버그도 잡고 추가개발도 해야하니...따라가기에도 얼마나 벅차겠습니까. 물론 한켠으론 vue-router 같은 핵심 패키지들과는 좀 협업해서 제때 내놨으면 좋았을텐데…라는 생각도 들긴 합니다.

 

다시 본론으로 돌아와서 2개의 vue-router는 Vue 3에서 바뀐 체계에 따르기 때문에 당연하게도 이전과는 사용법이 다릅니다. 아니, 뭐...비슷한데 조금 달라요. 왜냐하면, Vue 3.0에서 짜잔하고 등장한 Vue Application 때문에 그래요.

 

Vue 2.x에서 Vue 인스턴스로 하던 일을 3.0부터 Vue Application 인스턴스가 대신하니까요.

실제 코드로 정의하는 부분이 달라지는 건 당연한 거겠죠.

 

그래서, vue-router는 이에 대응하기 위해 추가적인 function을 만들어서 우리에게 제공해주고 있습니다.


2. 사용법

우선적으로 우리가 vue-router를 사용하기 위해서는 ‘설치’를 해줘야 합니다.

그리고 npm 패키지는 package.json에 명시된 규약에 따라 npm install 이라는 명령어로 패키지를 설치할 수 있죠. 구글에서 vue-router를 검색한 후 npm 패키지 사이트에 들어가보면 아래와 같이 가이드하고 있지만...

npm i vue-router

 

실제 위 코드를 썼을 때, 설치되는 버전은 Vue 2.x에 타게팅 된 버전이 설치되게 됩니다.

하지만 제가 제공하는 vue_starter 프로젝트는 Vue 3.x를 기반으로 하고 있기 때문에

해당 버전의 vue-router는 호환되지 않습니다.

 

버전을 올렸는데 호환이 안 된다는 게 말이 되냐...싶긴 합니다만 재차 언급하자면 현재는 Beta 버전이니까요. 정식 릴리즈 이후에는 해주지 않을까, 라는 기대는 합니다만 다들 아시다시피 기대가 크면 실망도 큽니다. 자, 그럼 우리는 어떻게 해야하느냐.

npm install 을 할 때 명시적으로 vue-router의 버전을 함께 적어주면 됩니다. 

npm install vue-router@next

이렇게 말이죠.

 

이제 우리는 Vue 3.x를 위해 개발되고 있는 vue-router를 설치했습니다.

(오…제가 Vue 포스팅을 시작한 9월 말까지만 해도 vue-router의 Status는 베타였는데, 이젠 Release Candidate로 바꼈네요, 조만간 정식으로 릴리즈 할 건가 봅니다.)

 

그럼 이제 설치를 했으니, 우리 프로젝트에서 vue-router를 ‘사용하겠다’라고 명시해주는 코드가 필요합니다. 기본적으로 vue-router는 프로젝트에서 전역으로 활용되게 됩니다. 그도 그럴것이 어떤 화면에서든지 URL의 path 값을 바탕으로 그에 매핑된 vue 컴포넌트(화면급의 컴포넌트)들이 호출되야 하니까요. 

 

그렇기때문에 Vue 프로젝트를 구성하는 모듈(파일)들의 진입점 역할을 하는 엔트리 파일에서 '사용하겠다'라는 코드가 정의되게 됩니다(이 규칙은, 단순히 vue-router 뿐만이 아니라 전역으로 쓰이는 컴포넌트, 혹은 차후에 포스팅 될 Store까지도 모두 엔트리 파일에 명시해줘야 합니다. 관리적인 측면에서도 여기저기 껌딱지처럼 두는 것보다는 훨씬 좋겠죠?)

 

import { createRouter, createWebHashHistory } from 'vue-router'
import router_main from './router/router_main'

let router = createRouter({
    history: createWebHashHistory(),
    routes: router_main
});

 

위와 같이 엔트리 파일에 vue-router를 위한 코드들이 있습니다(설명이 필요한 부분만 가져왔습니다)

 

우선, import { createRouter, createWebHashHistory } from ‘vue-router’ 를 봐주세요.

 

import & from 구문은 이전 포스팅에서 설명을 해드린 적이 있습니다.

여기서 주의깊에 보셔야 할 부분은 from 뒤에 따라오는 ‘vue-router’부분인데요.

일반적으로 위와같이 별다른 alias없이 이름만 정의된 모듈들은 대부분 npm install을 통해서 설치된 npm 패키지를 뜻하며 해당 패키지들은 node_modules 디렉토리 아래에 위치하게 됩니다(마치 maven 인스톨을 통해 설치한 라이브러리들이 .m2 디렉토리 아래에 repository로 모여드는 것과 비슷하게 말이죠)

 

그리고 중괄호를 통해서 vue-router 모듈로부터 필요한 2개의 function을 import 하고 사용하는데요.

각각의 function 아래와 같은 역할을 가지고 있습니다.

 

createRotuer : 실제 Vue Router 인스턴스를 생성할 때 쓰이는 function

createWebHashHistory : Vue Router를 통해 URL로 매핑된 vue 컴포넌트를 전환할 시에 필요한 히스토리 관리 기법을, 해시형으로 쓸 수 있게 해주는 인스턴스를 생성하는 function

 

여기서 히스토리에 대한 이야기가 잠깐 나와서 부연설명을 좀 드리고 넘어갈게요.

vue-router의 히스토리는 2가지 형식으로 관리가 됩니다.
예를 들어, 책을 빌리는 화면의 path가 /book/rent 라고 한다면,
위에서 사용한 해시 히스토리 기법은 전체 URL이 http://localhost:9090/#/book/rent 이구요.
또다른 히스토리 인스턴스 생성 function인 createWebHistory를 사용하면 http://localhost:9090/book/rent 와 같은 형태가 됩니다.

즉, Port 뒤에 해시태그가 붙냐, 붙지 않냐의 차이라고 볼 수 있는데요.
이 해시태그가 해주는 주요역할이 바로  ‘브라우저의 인식’입니다.

기본적으로 브라우저(IE든 크롬이든 Edge든 뭐든)는 # 이 붙은 URL의 뒷내용은 인지하지 못 합니다. 즉, 일반적인 경우에 # 뒤의 값이 변하더라도 브라우저는 새로운 화면을 로드하지 않습니다.
만약 우리가 http://localhost:9090/#/book/main 에서  
http://localhost:9090/#/book/rent 로 이동을 한다하더라도 브라우저는 화면이 ‘전환’되었다고 인지하지 못 한다는 겁니다.

대신에 vue-router가 그걸 인지하고 URL에 알맞게 매핑된 vue 컴포넌트를 호출해주는 것이죠.

반면에, 해시태그가 없는 경우, 브라우저 스스로가 URL이 바꼈음을 인지하게 됩니다.
그리고 그에따라서 알맞은 vue 컴포넌트를 호출하게 되는 것이구요.
예전에 jsp, html, php를 사용하던 방식이 바로 이와 같이 URL의 경로를 변경함으로써 브라우저가 이를 인식하고 그에 맞는 정적페이지들을 로딩해왔었죠. 이 방식이 바로 그에 착안한 방식이라고 할 수 있습니다.

다만, 이 경우엔 REST API를 위한 path와 URL상으로 부딪힐 염려가 있고, 혹은 적절하게 매핑된 vue 컴포넌트가 없을 경우 서버측으로부터의 404 에러가 발생할 수 있다는 점 기억해주세요.

 

그리고 다음 코드는 실제 path와 vue 컴포넌트간의 매핑이 정의된 

import router_main from ‘./router/router_main’ 입니다.

 

해당 파일은 앞선 포스팅에서 설명을 드렸었는데요. 실제 URL의 path로 쓰이는 값들과, 그 값들이 호출되었을 때 불려질 vue 컴포넌트들에 대한 내역을 정의하고 있는 파일입니다.

(물론 이건 제가 제공해드리는 vue_starter 기준이구요, 여러분은 다른 이름이겠죠?)

 

vue-router 인스턴스를 생성할 때, 당연히 path와 vue 컴포넌트가 어떻게 매핑이 되어있는지를 전달해줘야 실제 동작의 기반으로 삼을 수 있기 때문에 위와 같이 import를 해서 모듈로 불러오는 거구요.

 

가져온 router_main.js 파일의 내용은 다음 코드에 기입된 것과 같이 createRouter function의 파라미터로 사용되게 됩니다.

let router = createRouter({
    history: createWebHashHistory(),
    routes: router_main
});

 

그리고 createRouter의 파라미터 중 나머지 하나가 바로 위에서 언급했던 히스토리를 위한 createWebHashHistory 죠.

 

아, 여러분 이쯤에서 잊으셨을법한 포인트를 짚어드릴게요.

우리의 최종목표는 Vue입니다. vue-router는 Vue.js를 잘 활용할 수 있도록 도와주는 도구에 불과하죠.

그렇기에 createRouter로 생성한 vue-router 인스턴스를, createApp function을 통해 생성한 Vue Application 인스턴스에게 전달해줘야만 실제로 사용을 할 수 있게 됩니다.

 

바로, 다음과 같은 코드로 말이죠

let router = createRouter({
    history: createWebHashHistory(),
    routes: router_main
});

const app = createApp(App);
app.use(router);

자, 여기까지했으면 이제 vue 컴포넌트 어디서든지 <router-view> 라는 태그를 <template> 영역에서 사용하시면, 엔트리 파일에서 정의하고 생성한 vue-router 인스턴스가 그 위치에서 활용되게 됩니다.


3. 라우터의 이동

이제 거의 다 왔습니다. 조금만 더 힘을 내주세요.

 

 

저번 편을 유심히 오셨던 분이라면, 별첨으로 넣었던 -1 인덱스에서 이런 코드를 보신 적이 있을 겁니다.

moveTo(url){ 
    this.$router.push(url); 
}

 

결론부터 말하자면, this.$router 라는 전역 객체가 가지고 있는 push 함수를 통해서 라우터를 이동을 하게 됩니다, 전달되는 파라미터의 경로로 말이죠.

 

그리고 해당 경로는 path와 component를 페어로 정의한 router_main.js에 있는 규칙에 따릅니다.

위 코드에서는 전달 된 url의 경로로 움직이게 되어있죠? 해당 함수의 호출부는 아래와 같습니다.

<div class="gnb-menu" v-for="(menu, idx) in gnbMenu" 
    :key="idx" @click="moveTo(menu.url)">
    {{menu.name}}
</div>

위 코드에서 활용된 gnbMenu라는 배열은 아래와 같습니다.

gnbMenu: [
    {name: 'Intro', url: '/main/intro'},
    {name: 'Front-end', url: '/main/front'},
    {name: 'Back-end', url: '/main/back'}
]

아마, Vue.js의 문법이라 이게 뭐야, 라고 생각하실 분들을 위해 간단히 설명하자면 gnbMenu라는 배열이 가지고 있는 수만큼 for문(v-for)이 돌면서 위의 <div>태그를 그려주게 되는겁니다. 그리고 해당 <div>태그를 클릭했을 시에, moveTo라는 메서드를 호출하구요. 호출시에 파라미터로 gnbMenu의 인덱스들이 가지고 있는 url 값을 전달하고 있네요.

 

그래서 'Front-end'라는 라벨을 클릭하면 /main/front 경로로 this.$router.push('/main/front')를 호출하게 되구요. 그에 맞게 router_main.js 파일에 매핑된 frontEnd.vue component가 호출되게 되는거죠.

 

push외에 또 다른 라우터 이동방법도 있습니다.

바로 go 인데요. push함수와의 차이점은 전달되는 파라미터가 문자열로 이루어진 path의 형태가 아니라 숫자라는 점인데요. 이 숫자는, vue router 인스턴스가 내부적으로 가지고 있는 history에 기초하여 움직이게 되어있습니다.

 

즉, this.$router.go(-1) 을 호출하게되면 이전 페이지로.

this.$router.go(1)으로 호출하게되면 다음 페이지로 이동하게 되는 간단한 로직인거죠

(당연하게도 너무 많은 수, 혹은 너무 적은 수를 전달하게되면 해당 히스토리가 없을 가능성이 높기때문에 정상적으로 페이지 이동을 하지 않습니다)


4. param과 query

이제 특정 path 값을 this.$router 객체가 기진 함수에 전달함으로써,

해당 서비스에 화면전환을 요청 할 수 있게 되었습니다. 제가 제공해드리는 코드 기준으로보자면, 위에서도 간략히 설명드렸지만 gnbFrame.vue에 있는 gnbMenu를 기반으로 동작을 하게 되어있는거죠

(아마도, 실제 서비스를 개발하신다면 이런 메뉴체계 자체도 REST API를 통해서 서버로부터 가져올거고, 약속된 데이터의 형태를 바탕으로 GNB 및 LNB를 구성해주시면 되겠습니다)

 

자, 그럼 여기서 한 걸음 더 나아가보도록 하겠습니다.

 

vue router의 path라는 건, 늘 고정된 형식이진 않습니다.

path를 전달하면서 화면이 매핑된 컴포넌트로 전환이 될 때,

이전 화면에서 다음 화면으로 전달을 해줘야 하는 값이 있을 수도 있습니다.

 

예를 들어, 1번 화면은 사용자의 목록을 보는 화면이고, 여기서 사용자의 이름을 클릭하면 그 사용자의 상세데이터를 보는 2번 화면으로 이동한다고 가정해보겠습니다. 그렇다면, 사용자목록에서 선택한 사용자의 정보…그러니까 보편적인 경우라면 사용자의 ID가 될텐데요. 이 ID를 2번 화면으로 넘겨줘야 하는 경우가 있을 수 있다는거죠.

 

1번 화면의 사용자의 목록을 보는 path가 /user/list 였고

2번 화면의 특정 사용자의 상세 데이터를 보는 path가 /user/detail 라고 했을 때,

 

2번의 path인 /user/detail은 아래와 같은 2가지 형태를 위함으로써

1번 화면에서 사용자의 ID 정보를 넘겨 줄 수 있습니다.

/user/detail?userId=grey

/user/detail/grey

 

이러한 경우를 각각 vue router의 query 방식, param 방식이라고 부르는데요.

일반적으로 우리는 웹개발을 할 때, 종종 URL의 path 영역 뒤에 물음표가 있는 형태를 봐왔을 겁니다.

이는 SPA든, 아니든 상관없이 아주 흔하게 볼 수 있는 형태인데요. 물음표 뒤에  붙은 것들은 키&밸류 형태를 띄게되어있구요. 이러한 형태의 데이터들 우리는 query라고 부릅니다.

 

그리고 이렇게 전달 된 query 값들은 아래와 같이 발췌해서 코드에서 사용되게 됩니다

<div class="title"><h3>Router Example</h3></div>
<div><h4>URL Query : {{$route.query.userName}}</h4></div>

 

또 한 가지 방식은 param 인데요.

query방식은 URL을 보면 누구나 아, 저게 query구나. 라고 짐작을 할 수 있지만 param 방식은 한 번에 이게 param 방식이라는 걸 알아채기 힘듭니다. 왜냐면, 실제 사용되는 URL에 아주 자연스럽게 녹아들어가 있기 때문이죠.

 

그래서 해당 방식은 애초에 vue router가 정의되는 js파일에 아래처럼 선정의가 되어있어야 합니다. 

{ path: '/router/:userId', component: RouterExample }

path의 정의방식이 조금 다른 게 보이시나요?

실제 값으로 쓰이는 param의 경우에는 path를 이루는 단어 앞에 콜론이 붙어서 정의되게 되고

해당 이름을 그대로 실제 코드에서 활용 할 수 있게 됩니다. 아래 코드 처럼 말이죠.

<div class="title"><h3>Router Example</h3></div>
<div><h4>URL Param : {{$route.params.userId}}</h4></div>
           

여기서 주의해주실 점은, this.$router와 this.$route가 각각 사용되고 있다는 건데요.

 

path를 파라미터로 전달함으로서 화면간의 이동을 제어할 때는 this.$router를 사용했구요. 

query나 param과 같이 전달 된 값을 활용하기위해 꺼낼 때는 this.$route를 활용했다는 걸 꼭 기억하시고 헷갈리지 말아주시기를 바랍니다.

 

이와같이 현재 화면에서 필요한 특정 ‘값’을 이전화면으로부터 넘겨받아야 하는 경우에는 query 혹은 param 방식을 때에따라 활용해주시면 됩니다

vue_starter 프로젝트에서 query와 param에 대한 예제는 home.vue 와 routerExample.vue 파일을 봐주시면 됩니다. 정확히는 homve.vue 에서 routerExample.vue로 이동하는 예시를 만들어두었습니다.

homve.vue에는 query와 param을 전달하는 부분.
routerExample.vue에는 전달된 query와 param을 사용하는 부분.

이렇게 나누어서 정의해두었습니다.

5. beforeEach

URL의 path가 변경됨에 따라서 이전페이지로부터 넘겨받아야 할 값을 전달하는 방식이, query와 param이었습니다. 그리고 여기서 설명 할 beforeEach는 바로 그 화면간의 전환 사이의 시기를 잡아채는 인터셉터라고 생각해주시면 됩니다.

 

즉, 사용자 목록을 보는 1번 화면과, 선택된 사용자의 상세 정보를 보는 2번화면.

1번에서 2번으로 넘어가는 그 중간지점이 beforeEach 인겁니다.

 

시간과 공간사이 그 어디쯤 아아ㅏㅏㅏ...

 

좀 더 자세히 말해보자면 beforeEach는 function입니다.

param과 query가 this.$route라는 vue router 인스턴스 내부의 객체였다면,

beforeEach는 이러한 vue router가 가지고 있는 function으로써 인터셉터의 역할을 수행합니다

(여담으로 beforeEach가 있으니 당연히 afterEach도 있습니다만…Vue.js를 3년째하고 있는 저로써도 있다는 것만 알 뿐 사용해 본 적이 없어서 굳이 설명하진 않을게요)

 

beforeEach를 사용할 때 주요하게 보셔야 할 지점은, 바로 파라미터입니다.

beforeEach는 function이라고 설명을 드렸고 그렇기에 파라미터를 가지는데요.

보통 아래와 같은 형태로 from, to, next 라는 이름을 사용합니다

(vue router의 beforeEach에 정의된 순서에 따라 각 값들이 매핑되는거지만, 가독성을 높이기 위해 각 파라미터가 가지는 값별로 저런 이름을 붙였습니다)

router.beforeEach((to,from,next) => {
    console.log(from.path + ' -> ' + to.path);
    next();
})

 

from은 말 그대로 이전 페이지의 정보를 가진 객체구요

to는 다음 페이지의 정보를 가진 객체입니다.

next는 콜백함수로 인터셉터 역할을 하는 beforeEach가 다음페이지로 넘어갈 지에 대한 처리를 해주게 됩니다. 음…쓰고보니 이 문장이 좀 헷갈려 보이네요. 예제로 봅시다.

 

 

아마 그림이 좀 더 이해가 쉬우실 거라고 생각합니다.

 

beforeEach의 역할이 인터셉터라고 했던만큼, 잡아챘으니 다음으로 보내주는 동작도 명시적으로 정의해야하며 바로 그 역할을 하는 것이 next라는 콜백함수입니다. 그래서 위의 예시코드와 같이 beforeEach 를 사용 할 경우 next(); 라는 함수를 반드시 호출해줘야 다음 페이지로 넘어갈 수 있습니다.

 

그리고, 만약 인터셉트로 잡아채서 다른 경로로 보내고 싶다. 라고 한다면 next의 파라미터로 특정 path를 기입함으로써 해당 경로로 보내버릴 수 있는 겁니다. 이 경우에는 다음 페이지 역할을 하는 to 객체의 정보가 변경되었다고 vue router가 인식을 하고, to.path의 값이 바뀐 상태로 한 번 더 beforeEach를 호출하게 됩니다.

 

자…이 말인 즉슨, 여차하면 재귀라는 구렁텅이에 빠져서 여기도 저기도 갈 수 없게 된다는 말이라는 거 다들 눈치채셨을 겁니다. 한마디로 조심해서 잘 설계하고 개발해야 하는 영역이라는거죠.

 

어, 그래서 beforeEach가 언제 쓰이느냐, 에 대한 이야기를 해보자면.

이 function은 말그대로 인터셉터입니다. 예를 또 들어, 특정 사용자가 어떤 페이지로 들어가보고 싶은데 권한이 없을 수도 있잖아요? 관리자만 볼 수 있는 페이지가 있는데 일반 사용자가 브라우저에 다이렉트로 path 값을 변조해서 관리자 전용 페이지로 접근하려한다면 어떻게 막을 수 있을까요? 혹은 로그인하지 않은 사용자를 무조건 로그인 페이지로 보내야한다면 어떻게 할 수 있을까요?

 

이럴 때 쓰이는 게 바로 beforeEach를 통해 권한을 체크한다던가. 혹은 특정 조건을 충족하는 사람은 별도의 페이지로 리다이렉트시킨다던가. 하는 역할을 beforeEach가 담당하게 되는 겁니다.

 

위에서도 보여드렸던 코드이긴 합니다만, 간략한 예제를 보여드리기위해 현재 제 vue_starter 프로젝트에는 아래와 같이 엔트리파일인 main.js에 from -> to의 경로를 콘솔 로그에 찍게끔 만들어놓았구요. 실제 페이지이동시에 개발자도구를 띄워보시면 콘솔에 경로가 찍히는 걸 확인하실 수 있을겁니다.

let router = createRouter({
    history: createWebHashHistory(),
    routes: router_main
});

const app = createApp(App);
app.use(router);

router.beforeEach((to,from,next) => {
    console.log(from.path + ' -> ' + to.path);
    next();
})

 

예제코드와 같이 createRouter 메서드를 통해 생성된 vue router 인스턴스를 활용하여 beforeEach function을 정의해주시면 되겠습니다.

 

댓글
최근에 올라온 글
Total
Today
Yesterday