[Vue] Router 본문

Web/Vue

[Vue] Router

미니모아 2022. 9. 13. 08:56
반응형

Router

SPA

하나의 HTML 파일에서 자바스크립트로 어떤 것이 보일지를 컨트롤한다. 페이지 이동이 일어날 때에도 URL이 변경되는 것이 아니라 화면만 바뀌게 되는데 이 경우 현재 페이지의 정보를 알아낼 수 없으므로 문제가 된다. 따라서 URL이 변경될 때 하나의 SPA에서 해당 URL이 나타내는 페이지를 라우팅시켜줘야하고, 이 때문에 라우팅이 필요하다.

vue-router

npm 설치 후 라우터를 설정한다.

//main.js
import { createApp } from 'vue';
import {createRouter, createWebHistory} from 'vue-router';
import App from './App.vue';
import TeamsList from './components/teams/TeamsList.vue';
import UsersList from './components/users/UsersList.vue';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {path: '/teams', component: TeamsList}, // our-domain.com/teams..
    {path: '/users', component: UsersList}, // our-domain.com/teams..
  ]
});


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

routes에 path와 렌더링할 컴포넌트를 작성한다.

라우터 뷰가 렌더링할 위치는 router-vue로 지정해줄 수 있다.

<template>
  <the-navigation @set-page="setActivePage"></the-navigation>
  <main>
    <router-view></router-view>
  </main>
</template>

navigating with router-link

router-link 를 이용해 원하는 path로 이동 시킬 수 있다.

to속성에 이동하려는 path를 적는다.

<router-link to="/이동하려는 path">text</router-link>

router-link는 결국 a로 렌더링되므로 a 태그로 스타일링할 수 있다.

a {
  text-decoration: none;
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  color: white;
  padding: 0.5rem 1.5rem;
  display: inline-block;
}

a:hover,
a:active {
  color: #f1a80a;
  border-color: #f1a80a;
  background-color: #1a037e;
}

Styling Active Links

router-link는 active 되었을 경우 router-link-active라는 클래스를 가진다. 이를 이용해서 active 되었을 때 스타일링을 할 수 있다.

a:hover,
a:active,
a.router-link-active {
  color: #f1a80a;
  border-color: #f1a80a;
  background-color: #1a037e;
}

router의 linkActiveClass 옵션으로 active될 때 class 명을 커스텀할 수도 있다.

//main.js
const router = createRouter({
  history: createWebHistory(),
  routes: [
    {path: '/teams', component: TeamsList}, // our-domain.com/teams..
    {path: '/users', component: UsersList}, // our-domain.com/teams..
  ],
  linkActiveClass: 'active',
});

Programmatic Navigation

import UserItem from './UserItem.vue';

export default {
  components: {
    UserItem,
  },
  inject: ['users'],
  methods: {
    confirmInput() {
      // do something
      this.$router.push(' /teams');

    }
  }
};

Passing data with Rotue Params

//main.js
const router = createRouter({
  history: createWebHistory(),
  routes: [
    {path: '/teams', component: TeamsList}, // our-domain.com/teams..
    {path: '/users', component: UsersList}, // our-domain.com/teams..
    {path: '/teams/:teamId', component: TeamMember}, // our-domain.com/teams..
  ],
});

this.$route.param으로 파라미터를 가져올 수 있다.

import UserItem from '../users/UserItem.vue';

export default {
  inject: ['users', 'teams'],
  components: {
    UserItem
  },
  data() {
    return {
      teamName: '',
      members: [],
    };
  },
  created() {
    //this.$route.path // /teams/t1
    const teamId = this.$route.params.teamId;
    const selectedTeam = this.teams.find(team => team.id === teamId);
    const members = selectedTeam.members;
    const selectedMembers = [];
    for (const member of members) {
      const selectedUser =  this.users.find(user => user.id === member);
      selectedMembers.push(selectedUser);
    }
    this.members = selectedMembers;
    this.teamName = selectedTeam.name;
  }
};

Updating Params Data with Watcher

import UserItem from '../users/UserItem.vue';

export default {
  inject: ['users', 'teams'],
  components: {
    UserItem
  },
  data() {
    return {
      teamName: '',
      members: [],
    };
  },
  methods: {
    loadTeamMembers(route) {
      const teamId = route.params.teamId;
          const selectedTeam = this.teams.find(team => team.id === teamId);
          const members = selectedTeam.members;
          const selectedMembers = [];
          for (const member of members) {
            const selectedUser =  this.users.find(user => user.id === member);
            selectedMembers.push(selectedUser);
          }
      this.members = selectedMembers;
      this.teamName = selectedTeam.name;
    },
  },
  created() {
    this.loadTeamMembers(this.$route);
  },
  watch: {
    $route(newRoute) {
      this.loadTeamMembers(newRoute);
    }
  }
};

Passing Params as Props

파라미터를 prop으로 보낼 수도 있다.

//main.js
const router = createRouter({
  history: createWebHistory(),
  routes: [
    {path: '/teams', component: TeamsList}, // our-domain.com/teams..
    {path: '/users', component: UsersList}, // our-domain.com/teams..
    {path: '/teams/:teamId', component: TeamMember, props: true},
  ]
  }); 

Redirecting & Catch All routes

redirect

url을 변경한다.

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {path: '/', redirect: '/teams'},
]
})

alias

url를 변경하지 않고 원하는 페이지로 라우팅시킨다.

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // {path: '/', redirect: '/teams'},
    {path: '/teams', component: TeamsList, alias:'/'}, // our-domain.com/teams..
]
})

에러 페이지

path 외에 경로로 진입 시에 에러 페이지 컴포넌트를 보여주도록 설정할 수 있다.

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

import App from './App.vue';
import TeamsList from './components/teams/TeamsList.vue';
import UsersList from './components/users/UsersList.vue';
import TeamMembers from './components/teams/TeamMembers.vue';
import NotFound from './components/nav/NotFound.vue';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', redirect: '/teams' },
    { path: '/teams', component: TeamsList }, // our-domain.com/teams => TeamsList
    { path: '/users', component: UsersList },
    { path: '/teams/:teamId', component: TeamMembers, props: true },
    { path: '/:notFound(.*)', component: NotFound }
  ],
  linkActiveClass: 'active'
});

const app = createApp(App);

app.use(router);

app.mount('#app');

Nested Route

라우터 안에 children으로 nested Route를 추가할 수 있다.

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', redirect: '/teams' },
    { 
      path: '/teams',
      component: TeamsList,
      children: [
        { path: ':teamId', component: TeamMembers, props: true }, //teams/:teamId

    ] }, // our-domain.com/teams => TeamsList
    { path: '/users', component: UsersList },
    { path: '/:notFound(.*)', component: NotFound }
  ],
  linkActiveClass: 'active'
});

children이 렌더링 되기 위해서는 부모 컴포넌트 내부에 router-view를 추가해줘야한다. App.vue에서는 루트 path만 표시하기 때문이다.

Navigating with Name

path에 name을 지정해서 이동시에 해당 name을 이용할 수 있다. path가 변경되더라도 name으로 참조하기 때문에 더 편리하다

//main.js
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', redirect: '/teams' },
    { 
      name: 'teams',
      path: '/teams',
      component: TeamsList,
      children: [
        { 
          name: 'team-members',
          path: ':teamId',
          component: TeamMembers,
          props: true 
        }
        , //teams/:teamId

    ] }, // our-domain.com/teams => TeamsList
    { path: '/users', component: UsersList },
    { path: '/:notFound(.*)', component: NotFound }
  ],
  linkActiveClass: 'active'
});
<template>
  <li>
    <h3>{{ name }}</h3>
    <div class="team-members">{{ memberCount }} Members</div>
    <router-link :to="teamMembersLink">View Members</router-link>
  </li>
</template>

<script>
export default {
  props: ['id', 'name', 'memberCount'],
  computed: {
    teamMembersLink() {
      // return '/teams/' + this.id;
      return {name: 'team-members', params: {teamId: this.id}}
    }
  }
};
</script>

Query Parameter

query 옵션을 이용해 query parameter를 넘길 수 있다.

접근할 때는 this.$query.sort 와 같이 접근한다.

<script>
export default {
  props: ['id', 'name', 'memberCount'],
  computed: {
    teamMembersLink() {
      // return '/teams/' + this.id + ?srot=asc;
      return {
          name: 'team-members',
          params: {teamId: this.id},
          query:{ sort: 'asc'}
        }
    }
  }
};
</script>
  created() {
    // this.$route.path // /teams/t1
    this.loadTeamMembers(this.teamId);
    console.log(this.$route.query)
  },

Rendering Multiple Routes

하나의 화면에 서로 다른 루트를 렌더링하고 싶을 경우 해당 path에 component 옵션을 components로 정한 후 default와 다른 component를 설정해준다.

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', redirect: '/teams' },
    { 
      name: 'teams',
      path: '/teams',
      components: {
        default: TeamsList,
        footer: TeamsFooter
      },
      children: [
        { 
          name: 'team-members',
          path: ':teamId',
          component: TeamMembers,
          props: true 
        }
        , //teams/:teamId

    ] }, // our-domain.com/teams => TeamsList
    { 
      path: '/users',
      components: {
        default: UsersList,
        footer: UsersFooter,
      } },
    { path: '/:notFound(.*)', component: NotFound }
  ],
  linkActiveClass: 'active'
});
<template>
  <the-navigation></the-navigation>
  <main>
    <router-view></router-view>
  </main>
  <footer>
    <router-view name="footer"></router-view>
  </footer>
</template>

##

Controlling Scroll behavior

scrollBehavior(to, from, savedPosition) 메소드를 이용해 페이지를 이동했을 때 사용자의 스크롤 위치를 컨트롤할 수 있다.

  • to

  • from

  • saved Position : 백 버튼 누르기전에 사용자의 스크롤 위치

객체를 리턴해야하며 해당 객체는 페이지 이동시 어느 스크롤 위치로 이동시킬 건지 정해준다.

  scrollBehavior(to, from, savedPosition) {
    console.log(to, from, savedPosition)
    return {
      left: 0,
      top: 0,
    }
  }

savedPosition을 이용하면 뒤로가기 버튼을 눌렀을 때도 스크롤 위치가 변하지 않도록 할 수 있다.

  scrollBehavior(to, from, savedPosition) {
    console.log(to, from, savedPosition)
    if (savedPosition) {
      return savedPosition  
    }
    return {
      left: 0,
      top: 0,
    }
  }

Navigation Guard

네비게이션 전에 요청을 취소할 수 있다.

next(false)를 실행하면 네비게이션 요청이 취소된다.

beforeEach

router.beforeEach(function(to, from, next) {
  console.log('Global beforeEach');
  console.log(to, from);
  next();
})

강제로 이동할 path를 지정할 수도 있다.

router.beforeEach(function(to, from, next) {
  console.log('Global beforeEach');
  console.log(to, from);
  if (to.name === 'team-members') {
    next();
  } else {
    next({name: 'team-members', params: {teamId: 't2'}})
  }
  next();
})

로그인 사용자를 처리할 때 유용하게 사용할 수 있다.

path 레벨 과 컴포넌트 레벨에서도 beforeRouteEnter 를 이용하여 처리할 수 있다.

 const router = createRouter({
 { 
      path: '/users',
      components: {
        default: UsersList,
        footer: UsersFooter,
      },
      beforeEnter(to, from, next) {
        console.log('users beofreEnter');
        console.log(to, from);
        next();
      }
    },
 });
<script>
import UserItem from './UserItem.vue';

export default {
  components: {
    UserItem,
  },
  inject: ['users'],
  methods: {
    confirmInput() {
      // do something
      this.$router.push('/teams');
    }
  },
  beforeRouteEnter(to, from, next) {
    console.log('UserList cmp beforeRoueEnter');
    console.log(to, from ,next);
    next();
  }
};
</script>

afterEach

페이지 렌더링을 강제할 수 없지만 유저의 네비게이션 정보를 얻을 수 있다.

router.afterEach((to, from) => {
  //seding analytics data
  console.log('Global afterEach')
  console.log(to, from);
})

beforeRouteLeave

사용자가 페이지를 떠나기 전을 체크하여 특정 로직을 수행하도록 할 수 있다.

 beforeRouteLeave(to, from, next) {
    console.log('usersList Cmp beforeRouteLeave');
    console.log(to, from);
    if (this.changesSaved) {
      next();
    } else {
      const userWatnsToLeave = confirm('Are you sure? You got unsvaed changes!');
      next(userWatnsToLeave);
    }
  },

Metadata 이용하기

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', redirect: '/teams' },
    { 
      name: 'teams',
      path: '/teams',
      meta: { needsAuth: true },
})



router.beforeEach(function(to, from, next) {
  console.log('Global beforeEach');
  if(to.meata.needsAuth) {
    console.log('Needs Auth!')
  }
  // console.log(to, from);
  // if (to.name === 'team-members') {
  //   next();
  // } else {
  //   next({name: 'team-members', params: {teamId: 't2'}})
  // }
  next();
})
반응형

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

[Vue] Options API => Composition API: Lifecycle  (0) 2022.10.21
[Vue] Options API => Composition API  (0) 2022.10.21
[Vue] Vuex  (0) 2022.09.13
[Vue] v-model  (0) 2022.08.11
[Vue] 뷰 CLI에서 사용하는 NPM  (0) 2022.08.08
Comments