72 뷰 라우터 기본개념잡기

source: categories/study/vue-experiance/vue-experiance_9-72.md

72 뷰 라우터 기본개념잡기

  • www.yourwebsite.com/user/index.html
    1. /root 폴더에서
    2. /user 폴더를 찾고 /user 폴더에서
    3. index.html 파일을 찾아 렌더링한다.
  • vue.js에서는 이러한 방식으로 주소를 컨트롤하지 않는다.
    1. www.yourwebsite.com/user 아까와 다르게 뒤에 index.html 주소가 안 붙어있다.
    2. 여기서 /user 이 주소를 vue-router가 캐치를 합니다.
    3. vue-router가 가지고 있는 라우터 중에서 /user를 가지고있는 라우터를 찾고 이 라우터에 매칭시켜놓은 component를 찾습니다.
      • component는 당연히 하나의 컴포넌트로만 이루어져있지 않고 여러 컴포넌트를 import해오면서 이루어져있을겁니다.
    4. 여튼 매칭시켜놓은 컴포넌트가 /user를 통해서 라우터에 전달이돼고 라우터가 전달받은 컴포넌트들을 화면에 뿌려줍니다.
    5. 이때 전달받은 컴포넌트들을 뿌려줄 장소가 필요합니다. (router-view)
  • 좀 더 깊게 들어가면 할 이야기가 더 많겠지만 기초 내용을 다루는 이 시점에서는 이런식으로 동작하는구나 까지정도만 인식하시고 vue-router에 대한 공부를 시작하시면 됩니다.


// src/router.js
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';

Vue.use(Router);

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
    }
  ]
})




// src/router.js
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';

Vue.use(Router);

const About = () => import(/* webpackChunkName: "about" */ './views/About.vue');

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: About,
    }
  ]
})




<template>
<div>
  <button type="button" @click="$router.push({ name: 'home' })">home</button>
  <button type="button" @click="$router.push({ name: 'about' })">about</button>
</div>
</template>

<script>
export default {
  name: 'ExampleComponent',
}
</script>




<template>
<div>
  <button type="button" @click="$router.push({ path: '/' })">home</button>
  <button type="button" @click="$router.push({ path: '/about' })">about</button>
</div>
</template>

<script>
export default {
  name: 'ExampleComponent',
}
</script>




<template>
<div>
  <button type="button" @click="$router.push('/')">home</button>
  <button type="button" @click="$router.push('/about')">about</button>
</div>
</template>

<script>
export default {
  name: 'ExampleComponent',
}
</script>




<template>
<div>
  <button type="button" @click="$router.push({ name: 'home', query: {  }, params: {  } })">home</button>
  <button type="button" @click="$router.push({ name: 'about' })">about</button>
</div>
</template>

<script>
export default {
  name: 'ExampleComponent',
}
</script>




<template>
<div>
  <router-link
    :to="{ name: 'home' }"
  >home</router-link>
</div>
</template>

<script>
export default {
  name: 'ExampleComponent',
}
</script>


vuetify router 적용



<template>
<div>
  <!-- home의 path는 / -->
  <!-- about의 path는 /about -->
  <!-- about 페이지가 활성화되도 home path랑도 겹침, 즉 home 버튼에도 active class가 들어간다. -->
  <!-- 이를 막고싶다면, active class를 정확한 경로로 매칭시키고싶다면 exact 속성 활용 -->
  <v-list-tile
    router
    exact
    :to="{ name: 'home' }"
  >
    home
  </v-list-tile>
  <v-list-tile
    router
    exact
    :to="{ name: 'about' }"
  >
    about
  </v-list-tile>
</div>
</template>

<script>
export default {
  name: 'ExampleComponent',
}
</script>


vue-router 옵션 설정



import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './views/Home.vue';

Vue.use(Router);

const About = () => import(/* webpackChunkName: "about" */ './views/About.vue');

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

const router = new VueRouter({
  routes,
  mode: 'history',
  linkExactActiveClass: '_on',
  linkActiveClass: '_on',
});


히스토리 모드, 해시 모드

  • mode: 'history'를 지우면 주소창에 /#/이 붙는 것을 볼 수 있다.
    • 라우터의 기본 모드는 해시 모드이다.
    • 해시 태그 뒤에있는 것을 주소로 인지한다. localhost:8000/#/about /about을 인식해서 about 페이지로 이동한다.

값 전달하기 - parameters

  • localhost:8000/value에서 value를 하나의 값으로 받을 수 있는 방법에 대해 알아보겠다.


// src/router.js
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';

Vue.use(Router);

const About = () => import(/* webpackChunkName: "about" */ './views/About.vue');
const Users = () => import(/* webpackChunkName: "users" */ './views/Users.vue');

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: About,
    },
    {
      path: '/users/:userId',
      name: 'users',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: Users,
    }
  ]
})




<template>
<div>
  <v-list-tile
    router
    exact
    :to="{ name: 'home' }"
  >
    home
  </v-list-tile>
  <v-list-tile
    router
    exact
    :to="{ name: 'about' }"
  >
    about
  </v-list-tile>
  <v-list-tile
    router
    exact
    :to="{ name: 'users' }"
  >
    users
  </v-list-tile>
</div>
</template>

<script>
export default {
  name: 'ExampleComponent',
}
</script>




<template>
  <div>
    <h1>Users</h1>
    {{ userId }}
  </div>
</template>

<script>
export default {
  name: 'Users',
  computed: {
    userId() {
      return this.$route.params;
    }
  }
}
</script>


  • localhost:8000/users 접속시 접속 안됨
  • localhost:8000/users/123 Users 컴포넌트로 잘 접속됨
  • 화면에 {"userId": "123"} 노출

$router, $route

  • $routerrouter.js에 명시해놓은 모든 것이 들어가있다.
    • routes 프로퍼티엔 위에 설정해놓은 라우터들이 담겨있다.
    • new Router에 선언해준 속성, 옵션 값들이 담겨있다.
  • $route
    • params, query, 현재 주소 path

parameter 넘기는 방법



<template>
<div>
  <v-list-tile
    router
    exact
    :to="{ name: 'home' }"
  >
    home
  </v-list-tile>
  <v-list-tile
    router
    exact
    :to="{ name: 'about' }"
  >
    about
  </v-list-tile>
  <v-list-tile
    router
    exact
    :to="{ name: 'users', params: { userId: 4321, name: 'example' } }"
  >
    users
  </v-list-tile>
</div>
</template>

<script>
export default {
  name: 'ExampleComponent',
}
</script>


  • params: { userId: 4321, name: 'example' }
  • path: '/users/:userId',
  • name은 라우터 path에 명시되어있지 않지만, 값이 제대로 넘어간다.
  • 하지만 새로고침시 name에 담긴 example 값은 유지가 안될 것이다.
    • path에 따로 선언 안되어있으므로.. 내 기억이 맞다면 그럴거임

정리

  • 여튼 path에 변수 설정하던지
  • params에 담아서 보내던지
  • 둘 중에 어떤거 하나만 써도 값을 전달할 수 있다. this.$route.params 여기에 다 담겨져있다.
  • 그런데 내 생각엔 둘 다 같이 쓰는게 좋을 거 같다. (새로고침 이슈 때문에라도.. 이 기억이 맞겠지..?)

  • 흠 그런데 params 값을 url에 노출시키면 안되는 경우..? 그럼 새로고침시 어떻게 값을 유지하지?
    • query쓰면 url에 노출되잖아?
    • params 값은 데이터 보안이랑은 큰 연관 없는 거 같은데.. url에 반드시 노출안되어야하는 민감한걸 FE 쪽에서 보통 안다루니..
      • 내 생각엔 이런 블로그 글들이 잘못된거 같은데.. 흠 여튼.. params를 새로고침시 유지하려면 path에도 /:variance 설정
  • params로 숫자를 넘겼을시, 새로고침하면 문자열 데이터로 바뀌나봄?
    • 맞아 그래서 이것 때문에 == 이런 데이터 유형은 비교 안하는 연산자를 썼나보네..
    • 그래도 == 이런 연산자는 지양..
    • 여튼 관련 이슈 블로그글..

값 전달하기 - query

  • localhost:8000/about?mode=form&name=hyungju


<template>
<div>
  <v-list-tile
    router
    exact
    :to="{ name: 'home' }"
  >
    home
  </v-list-tile>
  <v-list-tile
    router
    exact
    :to="{ name: 'about' }"
  >
    about
  </v-list-tile>
  <v-list-tile
    router
    exact
    :to="{ 
      name: 'users', 
      params: { userId: 4321, name: 'example' },
      query: { group: 'member', category: 'trial' },
    }"
  >
    users
  </v-list-tile>
</div>
</template>

<script>
export default {
  name: 'ExampleComponent',
}
</script>




<template>
  <div>
    <h1>Users</h1>
    {{ $route.query }}
  </div>
</template>

<script>
export default {
  name: 'Users',
}
</script>


  • localhost:8000/users/4321?group=member&category=trial
  • 화면에 { "group": "member", "category": "trial" } 노출

  • urllocalhost:8000/users/4321?name=hyungju&address=seoul 직접 입력
  • url로 직접 전달한 query 값, 받을 수 있음
Note
  • params를 쓸지 query로 쓸지 그 기준은?
  • query는 분류 느낌이고, params는 노출되도 상관없는 값 처리할 때 사용하면 될 거 같긴한데..
  • 이는 좀 더 생각해봐야될 듯

하위 경로 children

  • localhost:8000/home/service/service2 이렇게 경로를 깊게 들어가는걸 하위 경로라고 한다.
  • localhost:8000/users/123/edit
    • localhost:8000/users
    • localhost:8000/users/:id
    • localhost:8000/users/:id/edit
    • 이렇게 각 라우터를 다 따로 팔 수 있음
    • 하지만 이번엔 children이란 속성을 활용해서 한 라우터 안에 설정해보도록 하겠음


// src/router.js
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';

Vue.use(Router);

const About = () => import(/* webpackChunkName: "about" */ './views/About.vue');
const Users = () => import(/* webpackChunkName: "users" */ './views/Users.vue');
const UsersDetail = () => import(/* webpackChunkName: "users-detail" */ './views/UsersDetail.vue');
const UsersEdit = () => import(/* webpackChunkName: "users-edit" */ './views/UsersEdit.vue');

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
      component: About,
    },
    {
      path: '/users',
      name: 'users',
      component: Users,
      // 하위 경로들은 users라는 컴포넌트 내에서만 동작하는 개념
      // 전체 주소를 총괄하는 'users'라는 라우터가 하나 있고, 'users' 안에서만 동작하는 라우터가 하나 더 있다고 생각하면됨
      children: [
        {
          path: ':id',
          name: 'users-detail',
          component: UsersDetail,
        },
        {
          path: ':id/edit',
          name: 'users-edit',
          component: UsersEdit,
        }
      ]
    }
  ]
})




<template>
<div>
  <v-list-tile
    router
    exact
    :to="{ name: 'home' }"
  >
    home
  </v-list-tile>
  <v-list-tile
    router
    exact
    :to="{ name: 'about' }"
  >
    about
  </v-list-tile>
  <v-list-tile
    router
    exact
    :to="{ name: 'users' }"
  >
    users
  </v-list-tile>
</div>
</template>

<script>
export default {
  name: 'ExampleComponent',
}
</script>




<template>
  <div>
    <h1>Users</h1>
    <p>유저를 검색해 주세요.</p>
    <input type="text" placeholder="유저 번호를 입력하세요." v-model="userId">
    <button type="button" @click="$router.push({ path: `users/${userId}` })">검색</button>
    <!-- localhost:8000/users/하위경로 : 이 하위 경로는 아래 router-view 에서만 동작함 -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'Users',
  data() {
    return {
      userId: null,
    }
  }
}
</script>




<template>
  <div>
    <h1>Users</h1>
    <p>유저를 검색해 주세요.</p>
    <input type="text" placeholder="유저 번호를 입력하세요." v-model="userId">
    <button type="button" @click="$router.push({ name: 'users-detail', params: { id: userId } })">검색</button>
    <!-- localhost:8000/users/하위경로 : 이 하위 경로는 아래 router-view 에서만 동작함 -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'Users',
  data() {
    return {
      userId: null,
    }
  }
}
</script>




<template>
  <div>
    <h1>Users Detail</h1>
    유저 번호: {{ userId }}
    <button type="button" @click="edit">수정</button>
  </div>
</template>

<script>
export default {
  name: 'UsersDetail',
  computed: {
    userId() {
      return this.$route.params.id;
    }
  },
  methods: {
    edit() {
      // 아래 두개는 똑같은 동작을 한다.
      // 라우터는 같은 Level(수준)에서 이동.
      // this.$router.push({ name: 'users-edit' });
      this.$router.push({ path: `${this.userId}/edit` });
    },
  }
}
</script>




<template>
  <div>
    <h1>Users Edit</h1>
    유저 번호: {{ userId }}
  </div>
</template>

<script>
export default {
  name: 'UsersEdit',
  computed: {
    userId() {
      return this.$route.params.id;
    }
  },
}
</script>


라우터 가드

  • 라우터를 지킨다.
    • 로그인을 안한 유저 > 마이 페이지 접근 불가
    • localhost:8000/mypage > 이런식으로 직접 주소창에 입력하여 접근할 때, 로그인을 안한 유저라면 접근 안되게 막는다던지,
    • localshot:8000/login > 아니면 이미 로그인한 유저가 로그인 페이지로 들어오려고할 때, 접근을 막는다던지,
  • 조건에 따라 라우터를 열어줄건지 말건지


// src/router.js
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';

Vue.use(Router);

const About = () => import(/* webpackChunkName: "about" */ './views/About.vue');
const Users = () => import(/* webpackChunkName: "users" */ './views/Users.vue');
const UsersDetail = () => import(/* webpackChunkName: "users-detail" */ './views/UsersDetail.vue');
const UsersEdit = () => import(/* webpackChunkName: "users-edit" */ './views/UsersEdit.vue');

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
      component: About,
    },
    {
      path: '/users',
      name: 'users',
      // vue-router에 있는 라우터 가드 역할을하는 속성들을 사용하여 라우터 가드 생성
      beforeEnter: (to, from, next) => {
        // to: 불러올 대상이되는 컴포넌트의 $route 객체
        // from: 지금 현재 컴포넌트의 $route 객체
        // next: 이 내부 로직들이 실행되고나서 어떤 라우터를 불러올지 실제로 정하는 함수
        // next(); // 이렇게 작성하면 to 즉, 불러올 대상이되는 컴포넌트를 불러온다.

        // 로그인 상태라면
        if (isUserLogin === true) {
          next();
        } else {
          next('/');
        }
      },
      component: Users,
      // 하위 경로들은 users라는 컴포넌트 내에서만 동작하는 개념
      // 전체 주소를 총괄하는 'users'라는 라우터가 하나 있고, 'users' 안에서만 동작하는 라우터가 하나 더 있다고 생각하면됨
      children: [
        {
          path: ':id',
          name: 'users-detail',
          component: UsersDetail,
        },
        {
          path: ':id/edit',
          name: 'users-edit',
          component: UsersEdit,
        }
      ]
    }
  ]
})


  • 만약에 beforeEnter를 통해서 해당 라우터 접근을 금지하고 싶다면, next('/'); 이런식으로 다른 라우터 주소를 입력해주시면 된다.
  • 실제로 위 beforeEnternext('/')를 입력해놓으면 users로 이동했는데 home으로 가시는걸 확인할 수 있다.
  • next('about')이라고 입력하면 users로 이동했는데 about 페이지로 이동하는걸 확인할 수 있다.
    • 음.. 그런데 next('/about)이라고 입력 안하고 next('about')이라고 입력했는데, 그럼 path, name 중 일치하는 곳으로 매칭시켜서 라우터 불러오는건가..?
    • 여튼 이거되면 활용성 클듯?
  • beforeEnter가 먼저 실행. beforeEnter 이후 next()가 호출되면 해당 라우터가 불러와지면서 해당 컴포넌트의 created 실행.
    • 순서 중요! 라이프사이클 훅!!!

router.js 파일 내에서 beforeEnter 실행하지 말고, 컴포넌트에서 router 가드 실행해보기



<template>
  <div>
    예시 컴포넌트
  </div>
</template>

<script>
export default {
  name: 'ExampleComponent',
  // 아까와 다르게 Route라는 단어가 포함되었다.
  beforeRouteEnter(to, from, next) {
    console.log('beforeEnter');
    next(); // next를 호출해야 라우트가 동작을 한다는거!!
  },
  // 여기선 한가지 훅을 더 사용할 수 있다.
  // 라우터가 이 컴포넌트를 떠나기 직전에 동작한다.
  beforeRouteLeave(to, from, next) {
    console.log('beforeLeave');
  },
  created() {
    console.log('created');
  },
  destroyed() {
    console.log('destroyed');
  },
}
</script>


  • 위와 같이 작성해도 beforeRouteEnter 먼저 실행, 그 다음에 created 실행
  • router.js에서 beforeEnter 작성한 것과 같이 가드 실행됨

  • 해당 컴포넌트에 들어가는 순간
    1. beforeEnter
    2. created
  • 해당 컴포넌트에서 나오는 순간
    1. beforeLeave
    2. destroyed
Note

라우터 가드

  • 사용자의 상태 또는 우리가 원하는 조건에 따라서 이 라우터를 사용자에게 열어줄건지 말건지, 이런 것들을 조절하기 위한 Vue-Router의 기능임

Vue-Router redirection

  • 우리가 라우터를 만들지 않은 곳으로 사용자가 주소를 입력해서 들어왔을 때, 그 사용자를 안전한 곳으로 리다이렉트 시키는 방법이다.


// src/router.js
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';

Vue.use(Router);

const About = () => import(/* webpackChunkName: "about" */ './views/About.vue');
const Users = () => import(/* webpackChunkName: "users" */ './views/Users.vue');
const UsersDetail = () => import(/* webpackChunkName: "users-detail" */ './views/UsersDetail.vue');
const UsersEdit = () => import(/* webpackChunkName: "users-edit" */ './views/UsersEdit.vue');

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
      component: About,
    },
    {
      path: '/users',
      name: 'users',
      // vue-router에 있는 라우터 가드 역할을하는 속성들을 사용하여 라우터 가드 생성
      beforeEnter: (to, from, next) => {
        // to: 불러올 대상이되는 컴포넌트의 $route 객체
        // from: 지금 현재 컴포넌트의 $route 객체
        // next: 이 내부 로직들이 실행되고나서 어떤 라우터를 불러올지 실제로 정하는 함수
        // next(); // 이렇게 작성하면 to 즉, 불러올 대상이되는 컴포넌트를 불러온다.

        // 로그인 상태라면
        if (isUserLogin === true) {
          next();
        } else {
          next('/');
        }
      },
      component: Users,
      // 하위 경로들은 users라는 컴포넌트 내에서만 동작하는 개념
      // 전체 주소를 총괄하는 'users'라는 라우터가 하나 있고, 'users' 안에서만 동작하는 라우터가 하나 더 있다고 생각하면됨
      children: [
        {
          path: ':id',
          name: 'users-detail',
          component: UsersDetail,
        },
        {
          path: ':id/edit',
          name: 'users-edit',
          component: UsersEdit,
        }
      ]
    },
    {
      path: '/redirect-me',
      redirect: { name: 'users' },
    },
    {
      path: '/*',
      redirect: { name: 'home' },
    }
  ]
})