FrontEnd/Vue

[Vue3] Vue Router

Grace 2023. 4. 17. 16:14

뷰 라우터 (Vue Router)

뷰 라우터는 Vue.js를 이용하여 싱글 페이지 애플리케이션(SPA)을 구현할 때 사용하는 Vue.js의 공식 라우터 입니다.

Getting Started | Vue Router

라우터란? (Router)

라우터라고 하면 일반적으로 네트워크간에 데이터를 전송하는 장치를 말합니다. 뷰에서 말하는 라우터는 쉽게 말해서 URL에 따라 어떤 페이지를 보여줄지 매핑해주는 라이브러리라고 보시면 될 것 같습니다.

예를 들어 “/home 경로로 요청이 들어왔을때 Home.vue 컴포넌트를 화면에 렌더링 해라!” 라는 역할을 수행하는 라이브러리라고 보시면 될 것 같습니다. 그리고 이때 /homeHome.vue 이러한 매핑정보를 라우트(Route)라고도 합니다.

라우트란? (Route)

어떤 URL에 대해 어떤 페이지를 표시해야 하는지에 대한 정보

설치

npm install vue-router

시작하기

HomeView.vueAboutView.vue라는 페이지용 컴포넌트를 만든후 ‘/’ 경로로 들어왔을 경우 HomeView.vue 페이지(컴포넌트)를 렌더링 하고 ‘/about’ 경로로 들어왔을 경우 AboutView.vue 페이지(컴포넌트)를 렌더링 하는 실습을 진행해 보도록 하겠습니다.

  • ‘/’HomeView.vue
  • ‘/about’AboutView.vue

페이지 컴포넌트 생성

HomeView.vueAboutView.vue 페이지(컴포넌트)를 생성해보도록 하겠습니다.

// src/views/HomeView.vue
<script setup></script>
<template>
  <h1>Home Page</h1>
</template>
// src/views/AboutView.vue
<script setup></script>
<template>
  <h1>About Page</h1>
</template>

라우트(routes) 정의

먼저 URL 요청에 대해 어떤 페이지(컴포넌트)를 보여줄지에 대한 매핑정보를 정의해보도록 하겠습니다.

// src/router/index.js
import HomeView from '@/views/HomeView.vue';
import AboutView from '@/views/AboutView.vue';

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView,
  },
  {
    path: '/about',
    name: 'about',
    component: AboutView,
  },
];

라우터(router) 설정

라우터를 설정해보도록 하겠습니다.

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '@/views/HomeView.vue';
import AboutView from '@/views/AboutView.vue';

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView,
  },
  {
    path: '/about',
    name: 'about',
    component: AboutView,
  },
];
const router = createRouter({
  history: createWebHistory('/'),
  routes,
});

export default router;

설정한 라우터 객체를 Vue 인스턴스에 추가해보도록 하겠습니다.

import { createApp } from 'vue';

import App from './App.vue';
import router from './router';

createApp(App).use(router).mount('#app');

app.use(router)를 호출 함으로써 컴포넌트 내부에서 $router, $route 객체에 접근할 수 있습니다.

네이게이션

뷰 라우터를 HTML과 JavaScript로 사용하는 방법에 대해 알아보도록 하겠습니다.

HTML

// src/App.vue
<script setup></script>

<template>
  <nav>
    <Routerlink to="/">Home</Routerlink>
    <span> | </span>
    <RouterLink to="/about">About</RouterLink>
  </nav>
  <main>
    <RouterView></RouterView>
  </main>
</template>
  • <RouterLink>이를 통해 Vue Router는 페이지를 리로딩 하지 않고 URL에 매핑된 페이지를 렌더링할 수 있습니다.
  • Vue Router 에서는 페이지를 이동할 때는 일반 a태그를 사용하는 대신 커스텀 컴포넌트인 <RouterLink>를 사용하여 다른 페이지 링크를 만들어야 합니다.
  • <RouterView>
  • <RouterView>는 URL에 매핑된 컴포넌트를 화면에 표시합니다.

JavaScript

위에서 router를 설정할 때 app.use(router)를 호출했습니다. 이렇게 호출 함으로써 모든 자식 컴포넌트에 router, route 같은 객체를 사용할 수 있습니다. 그리고 이러한 객체는 페이지 이동 또는 현재 활성 라우트(경로 매핑)정보 에 접근하는 데 사용할 수 있습니다.

  • router
    • Options API : this.$router
    • Composition API : useRouter()
    • template : $router
  • 라우터 인스턴스로 JavaScript에서 다른 페이지(컴포넌트)로 이동할 수 있다.
  • route
    • Options API : this.$route
    • Composition API : useRoute()
    • template : $route
  • 현재 활성 라우트 정보에 접근할 수 있다. (이 속성은 읽기 전용 입니다.)
<!-- HomeView.vue -->
<script setup>
import { useRoute, useRouter } from 'vue-router';

const router = useRouter();
const route = useRoute();
console.log('route.name: ', route.name);
console.log('route.path: ', route.path);
const goAboutPage = () => router.push('/about');
</script>
<template>
  <h1>Home Page</h1>
  <button @click="goAboutPage">About 페이지로 이동</button>
</template>
<!-- AboutView.vue -->
<script setup></script>
<template>
  <h1>About Page</h1>
  <ul>
    <li>$route.name: {{ $route.name }}</li>
    <li>$route.path: {{ $route.path }}</li>
  </ul>
  <button @click="$router.push('/')">Home 페이지로 이동</button>
</template>

components.d.ts

로컬 컴포넌트, 내장 컴포넌트, 기본 HTML 요소 구성 없이 Type-Checking을 사용할 수 있습니다.

전역 컴포넌트의 경우 GlobalComponents 인터페이스를 정의해야 합니다. 예를 들면 다음과 같습니다.

// components.d.ts
declare module '@vue/runtime-core' {
  export interface GlobalComponents {
    RouterLink: typeof import('vue-router')['RouterLink'];
    RouterView: typeof import('vue-router')['RouterView'];
  }
}

export {};

동적 라우트 매칭

주어진 패턴을 가진 라우트를 동일한 컴포넌트에 매핑해야하는 경우가 자주 있습니다. 예를 들어 사용자 목록(User List)/users와 같은 경로에 매핑되면 되지만 사용자 상세(User Detail)는 사용자 식별자 별로 같은 컴포넌트에 매핑 되어야 합니다. (예: /users/alice, /users/emma, ...UserComponent.vue)

이럴때 Vue Router에서는 경로에서 동적 세그먼트를 사용하여 해결할 수 있습니다. 이를 param이라고 합니다.

const User = {
  template: '<div>User</div>',
}

const routes = [
  { path: '/users/:id', component: User },
]

이제 /users/alice, /users/emma URL은 모두 같은 경로(’/users/:id’)에 매핑됩니다.

  • 동적 세그먼트는 콜론(:)으로 표시합니다.
  • 그리고 컴포넌트에서 동적 세그먼트의 값은 $route.params 필드로 접근할 수 있습니다.
const User = {
  template: '<div>User {{ $route.params.id }}</div>',
}

동일한 라우트에 여러 동적 세그먼트를 가질 수 있으며, $route.params 필드에 매핑됩니다.

pathURL example$route.params

/users/:username /users/alice { username: ‘alice’ }
/users/:username/posts/:postId /users/alice/posts/123 { username: ‘alice’, postId: ‘123’ }

query, hash

$route.params 외에도 $route 객체는 $route.query(쿼리스트링), $route.hash(해시태그) 등과 같은 다른 유용한 정보도 노출합니다.

URL example$route

/users?searchText=love { params: {...}, hash: '...', query: { searchText: love } }
/users/alice#profile { params: {...}, hash: 'profile', query: { ... } }

다른 유용한 정보를 더 확인하시려면 API Reference를 참고하세요.

404 Not Found Route

일반 파라미터(:id)는 슬래쉬(/)로 구분된 URL 사이의 문자만 일치시킵니다. 무엇이든 일치시키려면 param 바로 뒤에 괄호 안에 정규식(regexp)을 사용할 수 있습니다.

const routes = [
  // will match everything and put it under `$route.params.pathMatch`
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
  // will match anything starting with `/user-` and put it under `$route.params.afterUser`
  { path: '/user-:afterUser(.*)', component: UserGeneric },
]

프로그래밍 방식 네비게이션

<RouterLink>를 사용하여 선언적 네비게이션용 anchor 태그를 사용하는 것 외에도 라우터 인스턴스 메소드를 사용하여 프로그래밍 방식으로 이를 수행 할 수 있습니다.

router.push

다른 URL로 이동하려면 router.push를 사용할 수 있습니다. 이 메소드는 새로운 항목을 히스토리 스택에 넣기 때문에 사용자가 브라우저의 뒤로 가기 버튼을 클릭하면 이전 URL로 이동하게 됩니다.

이 메소드는 <RouterLink>를 클릭 할 때 내부적으로 호출되는 메소드이므로 <RouterLink :to=”...”>를 클릭하면 router.push(...)를 호출하는 것과 같습니다

선언적 방식프로그래밍 방식

<RouterLink :to=”...”> router.push(...)
<RouterLink :to="..."></RouterLink>

router.push 파라미터는 문자열 경로 또는 객체가 될 수 있습니다.

// 리터럴 문자열 경로
router.push('/users/eduardo')

// 경로가 있는 개체
router.push({ path: '/users/eduardo' })

// 이름을 가지는 라우트
router.push({ name: 'user', params: { username: 'eduardo' } })

// 쿼리와 함께 사용, 결과적으로 /register?plan=private가 됩니다.
router.push({ path: '/register', query: { plan: 'private' } })

// 해시와 함께 사용, 결과적으로 /about#team가 됩니다.
router.push({ path: '/about', hash: '#team' })
const username = 'eduardo'
// URL을 수동으로 작성할 수 있지만 인코딩을 직접 처리해야 합니다.
router.push(`/user/${username}`) // -> /user/eduardo
// 위와 동일
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 가능하면 `name`과 `params`를 사용하여 자동 URL 인코딩의 이점을 얻습니다.
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params`는 `path`와 함께 사용할 수 없습니다.
router.push({ path: '/user', params: { username } }) // -> /user

router.replace

router.push와 같은 역할을 하지만 유일한 차이는 새로운 히스토리 항목에 추가하지 않고 탐색한다는 것입니다. 이름에서 알 수 있듯이 현재 항목을 대체합니다.

선언적 방식프로그래밍 방식

<router-link :to=”...” replace> router.replace(...)

router.push 메소드에 replace: true속성을 추가하여 동일하게 동작시킬 수 있습니다.

router.push({ path: '/home', replace: true })
// equivalent to
router.replace({ path: '/home' })

router.go(n)

이 메소드는 window.history.go(n)와 비슷하게 히스토리 스택에서 앞으로 또는 뒤로 이동하는 단계를 나타내는 하나의 정수를 매개 변수로 사용합니다.

// 한 단계 앞으로 갑니다. history.forward()와 같습니다. history.forward()와 같습니다.
router.go(1)

// 한 단계 뒤로 갑니다. history.back()와 같습니다.
router.go(-1)

// 3 단계 앞으로 갑니다.
router.go(3)

// 지정한 만큼의 기록이 없으면 자동으로 실패 합니다.
router.go(-100)
router.go(100)

Params 변경 사항에 반응하기

매개 변수와 함께 라우트를 사용할 때 주의 해야할 점은 사용자가 /users/alice에서 /users/emma로 이동할 때 동일한 컴포넌트 인스턴스가 재사용된다는 것입니다. 왜냐하면 두 라우트 모두 동일한 컴포넌트를 렌더링하므로 이전 인스턴스를 삭제 한 다음 새 인스턴스를 만드는 것보다 효율적입니다. 그러나 이는 또한 컴포넌트의 라이프 사이클 훅이 호출되지 않음을 의미합니다.

이렇게 동일한 컴포넌트를 재사용할 때 URL이 변경되게 되면 라이프사이클 훅이 호출되지 않기 때문에 훅에서 하던 일을 할 수 없습니다.

이럴 때는 Watcher(watch, watchEfffect) 또는 beforeRouteUpdate navigation guard를 사용하여 params와 같은 URL 변경사항에 반응할 수 있습니다.

watch를 통한 params 반응하기

// <script setup>
import { useRoute, watch } from 'vue-router';

const route = useRoute();

watch(
  () => route.params,
  (toParams, previousParams) => {
		// working
  }
);

beforeRouteUpdate

동일한 컴포넌트를 재사용할 때 URL이 변경되는 경우 호출됩니다.

Options API

export default {
	beforeRouteUpdate(to, from) {
		// working
		this.userData = await fetchUser(to.params.id)
	}
}

Composition API

// <script setup>
import { onBeforeRouteUpdate } from 'vue-router';
onBeforeRouteUpdate((to, from) => {
  console.log('onBeforeRouteUpdate');
});

이름을 가지는 라우트 (Named Routes)

Router 인스턴스를 생성할 때 path와 함께 name을 지정할 수 있습니다.

const routes = [
  {
    path: '/user/:username',
    name: 'user',
    component: User
  }
]

이름을 가진 라우트에 링크하려면, 객체를 router-link 컴포넌트의 to prop로 전달할 수 있습니다.

<router-link :to="{ name: 'user', params: { username: 'erina' }}">
  User
</router-link>

이것은 router.push()와 프로그램적으로 사용되는 것과 정확히 같은 객체입니다.

router.push({ name: 'user', params: { username: 'erina' } })

두 경우 모두 라우터는 /user/erina 경로로 이동합니다.

이름을 가지는 뷰 (Named Views)

때로는 여러 개의 뷰(router-view)를 중첩하지 않고 동시에 표시해야 하는 경우가 있습니다. 이때 router-view에 이름을 지정하여 여러개의 router-view를 사용할 수 있습니다. 그리고 이름이 없는 router-viewdefault가 이름으로 주어집니다.

<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>

뷰는 컴포넌트를 사용하여 렌더링 되므로 여러 뷰에는 동일한 라우트에 대해 여러 컴포넌트가 필요합니다. components(s를 붙입니다) 옵션을 사용해야합니다.

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      components: {
        default: Home,
        // short for LeftSidebar: LeftSidebar
        LeftSidebar,
        // they match the `name` attribute on `<router-view>`
        RightSidebar,
      },
    },
  ],
})

중첩된 라우트(Nested Routes)

실제 앱 UI는 일반적으로 여러 단계로 중첩 된 컴포넌트로 이루어져 있습니다. URL의 세그먼트가 중첩 된 컴포넌트의 특정 구조와 일치한다는 것은 매우 일반적입니다. 예를 들면 다음과 같습니다.

/user/johnny/profile                  /user/johnny/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

vue-router를 사용하면 중첩 된 라우트 구성을 사용하여 이 관계를 표현하는 것이 매우 간단합니다.

<!-- App.vue -->
<div id="app">
	<router-view></router-view>
</div>
<!-- User.vue -->
<div class="user">
	<h2>User {{ $route.params.id }}</h2>
</div>
// router/index.js
const routes = [
  {
    path: '/user/:id',
    component: User,
  },
]

App.vue에 있는 <router-view>는 최상위 router-view입니다. 이 router-viewroutes의 최상위 path와 일치하는 컴포넌트(User.vue)가 렌더링 됩니다.

그리고 User.vue 컴포넌트 내부에 중첩된 <router-view>를 선언할 수 있습니다.

<!-- User.vue -->
<div class="user">
	<h2>User {{ $route.params.id }}</h2>
	<router-view></router-view>
</div>

그리고 컴포넌트를 이 중첩된 <router-view>로 렌더링하려면 routes 안의 children 옵션을 사용해야 합니다.

// router/index.js
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        path: 'profile',
        component: UserProfile,
      },
      {
        path: 'posts',
        component: UserPosts,
      },
    ],
  },
]
<!-- UserProfile.vue -->
<div class="user-profile">
	User Profile
</div>
<!-- UserPosts.vue -->
<div class="user-posts">
	User Posts
</div>

참고

  • /로 시작하는 중첩 경로는 루트 경로로 처리됩니다. 이를 통해 중첩 URL을 사용하지 않고도 컴포넌트 중첩을 활용할 수 있습니다.
  • 위 routes 설정으로 보면 /users/alice로 방문 했을 때 User 컴포넌트에 있는 중첩된 <router-view>에는 아무것도 렌더링 되지 않습니다. 이러한 경우 빈 중첩 경로를 제공할 수 있습니다.
  • const routes = [ { path: '/user/:id', component: User, children: [ { path: '', component: UserHome }, // ...other sub routes ], }, ]

라우트 컴포넌트에 속성 전달

컴포넌트에서 $route객체를 사용하면 특정 URL에서만 사용할 수 있게되어 라우트와 강한 결합을 만듭니다. 즉 컴포넌트의 유연성이 제한됩니다. 이러한 결합이 꼭 나쁜 것은 아니지만 props옵션으로 이 동작을 분리할 수 있습니다.

컴포넌트와 라우터 속성을 분리하려면 다음과 같이 하십시오.

라우트에 의존된 컴포넌트

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}
const routes = [{ path: '/user/:id', component: User }]

라우트 의존도 해제

const User = {
  // make sure to add a prop named exactly like the route param
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const routes = [{ path: '/user/:id', component: User, props: true }]

이를 통해 어디서나 컴포넌트를 사용할 수 있으므로 컴포넌트 재사용 및 테스트하기가 더 쉽습니다.

Boolean 모드

props를 true로 설정하면 route.params가 컴포넌트 props로 설정됩니다.

Named views

이름을 가지는 뷰(Named Views)가 있는 경우 각 Named Views에 대한 props 옵션 을 정의해야 합니다 .

const routes = [
  {
    path: '/user/:id',
    components: { default: User, sidebar: Sidebar },
    props: { default: true, sidebar: false }
  }
]

객체 모드

props가 객체일때 컴포넌트 props가 있는 그대로 설정됩니다. props가 정적일 때 유용합니다.

const routes = [
  {
    path: '/promotion/from-newsletter',
    component: Promotion,
    props: { newsletterPopup: false }
  }
]

함수 모드

props를 반환하는 함수를 만들 수 있습니다. 이를 통해 전달인자를 다른 타입으로 캐스팅하고 적정인 값을 라우트 기반 값과 결합됩니다.

const routes = [
  {
    path: '/search',
    component: SearchUser,
    props: route => ({ query: route.query.q })
  }
]

다양한 history 모드

Router 인스턴스를 생성할 때 history 옵션을 사용하면 다양한 history mode 중에서 선택할 수 있습니다.

Hash 모드

Vue Router를 통해 URL로 페이지를 전환할 때 히스토리 관리 기법를 해시(#)형으로 쓸 수 있게 해줍니다.

해시모드는 createWebHashHistory()를 사용하여 생성됩니다.

import { createRouter, createWebHashHistory } from 'vue-router'

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    //...
  ],
})

내부적으로 전달되는 실제 URL 앞에 해시 문자(#)를 사용합니다. URL의 이 섹션은 서버로 전송되지 않으므로 서버 수준에서 특별한 처리가 필요하지 않지만 SEO에 나쁜 영향을 미칩니다 . 걱정된다면 HTML5 모드(createWebHistory())를 사용하세요.

History 모드 (HTML5 모드)

Vue Router를 통해 URL로 페이지를 전환할 때 히스토리 관리 기법를 해시(#)없이 쓸 수 있게 해줍니다. Web API인 history.pushState()를 활용하여 페이지를 다시 로드하지 않고도 URL 탐색을 할 수 있습니다.

HTML5 모드는 createWebHistory()로 생성되며 권장 모드입니다.

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    //...
  ],
})

createWebHistory()를 사용하면 URL은 “정상"으로 보입니다.

하지만 우리의 앱이 적절한 서버 설정이 없는 단일 페이지 클라이언트 앱이기 때문에 사용자가 직접 http://oursite.com/user/id에 접속하면 404 오류가 발생합니다.

문제를 해결하려면 서버에 간단하게 포괄적인 대체 경로를 추가하기만 하면됩니다. URL이 정적 에셋과 일치하지 않으면 앱이 있는 동일한 index.html 페이지를 제공해야 합니다.

서버 설정 및 주의 사항

서버설정 및 주의 사항은 공식홈페이지를 참고하시는 것을 권장드립니다.

HTML5 히스토리 모드 | Vue Router

참고

'FrontEnd > Vue' 카테고리의 다른 글

[Pinia] 상태관리 라이브러리  (0) 2023.04.17
[Vue3] Composable API  (0) 2023.04.17
[Vue3] Dynamic component  (0) 2023.04.17
[Vue3] watch, watchEffect  (0) 2023.04.17
[Vue3] 양방향 바인딩  (0) 2023.04.17