# vue-router

# API

https://router.vuejs.org/api/ (opens new window)

# 基本的な使い方

<!-- ここにマッチしたコンポーネントが描写される -->
<router-view></router-view>

<!-- リンクを張りたいときは -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
const Foo = { template: '<div>foo</div>' };
const Bar = { template: '<div>bar</div>' };

const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar },
];

const router = new VueRouter({
  routes,
});

var app = new Vue({
  el: '#app',
  router,
});

router-linkには、ルートによってrouter-link-exact-activerouter-link-activeといったクラスが自動的に設定される。

コンポーネント側から vue-router を操作するには次のオブジェクトを使う。

# Dynamic Route Matching

次のようにすることで、ダイナミックルーティングを設定できる。 :username:post_idの部分は、this.$route.paramsとしてコンポーネントに渡される。

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

// /user/evan/post/123?page=2
//
// params => { username: 'evan', post_id: 123 }
// query => { page: 2 }

# params の変更を検知する

params が変更されただけでは、コンポーネントは再作成されない(再利用される)。 つまりライフサイクルメソッドは発動しない。 これを検知したいときは次のようにする。

const User = {
  watch: {
    $route(to, from) {},
  },
  // もしくはナビゲーションガードを使う
  beforeRouteUpdate(to, from, next) {},
};

# 様々なルート定義

vue-router はpath-to-regexp (opens new window)を使用しており、多様なルート定義ができる。詳細はドキュメントを参照。

# 優先順位

重複したルートが合った場合は、先に定義されている方が優先される。

# Nested Routes

下記のように設定すると、Userコンポーネントの中に配置した<router-view />の部分に指定したコンポーネントが描写される。

const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      { path: '', component: UserHome },
      {
        path: 'profile',
        component: UserProfile,
      },
      {
        path: 'posts',
        component: UserPosts,
      },
    ],
  },
];

# Programmatic Navigation

# router.push

router.push('home');
router.push({ path: 'home' });
router.push({ path: `/user/${userId}` });
router.push({ path: 'register', query: { plan: 'private' } });

// なお、pathを指定すると、paramsは無視される。
// paramsを使うときは、`path`の代わりに`name`を使うこと。
router.push({ name: 'user', params: { userId: 123 } });

# router.replace

history に履歴を残さずにルートを変更する。その他はpushと同じ。

# router.go

router.go(1);
router.go(-1);
router.go(3);

# Named Routes

ルートに名前をつけることができる。

routes = [
  {
    path: '/user/:userId',
    name: 'user',
    component: User,
  },
];
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

# Named Views

  • サイドバーの内容をページによって変更したいときなどは、router-viewに名前をつけることで対応する。
  • 名前をつけなかった View には自動的にdefaultという名前が与えられる。
  • componentではなくcomponentsなので注意
<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>
routes = [
  {
    path: '/',
    components: {
      default: Foo,
      a: Bar,
      b: Baz,
    },
  },
];

# Named View をネストする

例えば、次の例のように、/settings/profileのときだけ特定のコンポーネントを表示したい場合

/settings/emails                                       /settings/profile
+-----------------------------------+                  +------------------------------+
| UserSettings                      |                  | UserSettings                 |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | |  +------------>  | | Nav | UserProfile        | |
| |     +-------------------------+ |                  | |     +--------------------+ |
| |     |                         | |                  | |     | UserProfilePreview | |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
+-----------------------------------+                  +------------------------------+
<!-- UserSettings.vue -->
<router-view/>
<router-view name="helper"/>
{
  path: '/settings',
  // You could also have named views at the top
  component: UserSettings,
  children: [
    {
      path: 'emails',
      component: UserEmailsSubscriptions
    },
    {
      path: 'profile',
      components: {
        default: UserProfile,
        helper: UserProfilePreview
      }
    },
  ]
}

# Redirect and Alias

  • リダイレクトするには次のようにする。
  • なお、この場合、ナビゲーションガードはリダイレクト元には適用されないので注意。
routes = [
  // 通常のリダイレクト
  { path: '/a', redirect: '/b' },
  // Named Routeにリダイレクト
  { path: '/a', redirect: { name: 'foo' } },
  // ファンクションを使ってリダイレクト
  {
    path: '/a',
    redirect: to => {
      // TODO: toがなんなのかよくわからない
      // return redirect path/location here.
    },
  },
];

# Alias

リダイレクトと異なり、history を変更せず、ただ単に表示する内容をpathで指定したものに差し替える。 パスを変更するコストをかけずに、URL の構成を変えることができるので便利。

routes = [{ path: '/a', component: A, alias: '/b' }];

# ルートに関する情報を、$route ではなく Props として渡す

パラメータ類を$routeで取得するとコンポーネントの再利用性・テスト性が低くなるので、propsを使って取得するほうがよい。

BAD

// コンポーネント
const User = {
  template: '<div>User {{ $route.params.id }}</div>',
};
// vue-router設定
const router = new VueRouter({
  routes: [{ path: '/user/:id', component: User }],
});

GOOD

// コンポーネント
const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>',
};
// vue-router設定
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User, props: true },

    // Named Viewを使っている場合はそれぞれに指定する
    {
      path: '/user/:id',
      components: { default: User, sidebar: Sidebar },
      props: { default: true, sidebar: false },
    },
  ],
});

propsに指定できる値は下記の 3 種類がある

  • boolean
    • route.paramsが props として渡される
  • object
    • そのオブジェクトがそのまま props として渡される
  • function
    • 計算結果のオブジェクトが props として渡される
    • ルートが変更された時にのみ実行されるので、ステートフルにしないよう注意する
    • 例:(route) => ({ query: route.query.q }) }

# HTML5 History Mode

  • hash mode (デフォルト)
  • history(HTML5) mode

HTML5 モードを使うには次のように設定する。

const router = new VueRouter({
  mode: 'history',
});

HTML5 モードにした場合、サーバ側で、全てのルートへのリクエストに対しindex.htmlを返すように設定しておく必要があるので注意する。サーバ別の設定方法はこちら (opens new window)

この設定を行うと、404 ページが表示されなくなるため、下記の設定を行っておくこと。もしくは、サーバー側で、存在しない URL の場合に 404 を返す設定を行うこと。

routes = [{ path: '*', component: NotFoundComponent }];

# Global Guards

# beforeEach

全てのルート変更をキャッチする。

router.beforeEach((to, from, next) => {});

# beforeResolve

router.beforeResolveで登録する。beforeEachとの違いは次の通り。

  • Route の変更が承認される直前に実行される。
  • 全ての、in-Component Guard と、非同期コンポーネントが解決されたに実行される。

# afterEach

Route 変更の後に呼ばれる。nextは無いので変更を止めることはできない。

router.afterEach((to, from) => {});

# Per-Route Guard

Route 設定ごとのガード。beforeEnter

routes = [
  {
    path: '/foo',
    component: Foo,
    beforeEnter: (to, from, next) => {},
  },
];

# In-Component Guards

コンポーネントごとのガード

  • beforeRouteEnter

    • そのコンポーネントを使う Route が承認される前に呼ばれる
    • thisでコンポーネントインスタンスにアクセスできない(作られる前だから)
    • インスタンスへアクセスするには next コールバックを使う。ナビーゲーションの確定後に実行される。
      next(vm => {
        // `vm` を通じてコンポーネントインスタンスにアクセス
      });
      
  • beforeRouteUpdate

    • そのコンポーネントを使う Route が変更された場合に呼ばれる
    • 例)'/foo/1' => '/foo/2'
    • thisでコンポーネントインスタンスにアクセスできる
  • beforeRouteLeave

    • そのコンポーネントを使う Route から、別の Route に移動する前に呼ばれる
    • thisでコンポーネントインスタンスにアクセスできる
const MyComponent = {
  template: `...`,
  beforeRouteEnter(to, from, next) {},
  beforeRouteUpdate(to, from, next) {},
  beforeRouteLeave(to, from, next) {},
};

# ガードの呼び出し順まとめ

  1. ナビゲーションがトリガされる
  2. 非アクティブ化されたコンポーネントの beforeRouteLeave
  3. グローバル beforeEach
  4. 再利用されるコンポーネントの beforeRouteUpdate
  5. ルート設定内の beforeEnter
  6. 非同期ルートコンポーネントを解決する
  7. アクティブ化されたコンポーネントの beforeRouteEnter
  8. グローバル beforeResolve
  9. ナビゲーションが確定される
  10. グローバル afterEach フックを呼ぶ
  11. DOM 更新がトリガされる
  12. 生成されたインスンタンスによって beforeRouteEnter ガードで next に渡されたコールバックを呼ぶ

# 注意

params,queryだけの変更は、enver/leaveナビゲーションガードを発動しないので注意。

# Route Meta Fields

ルート設定にメタ情報をもたせることができる

routes = [
  {
    path: '/foo',
    component: Foo,
    meta: { requiresAuth: true },
  },
];

メタ情報はrouteオブジェクトのmatchedArray の各アイテムのmetaとして取得できる。

if (someRouteObject.matched.some(record => record.meta.requiresAuth)) {
  alert('require authengication');
}

# 詳細

  • routesコンフィグに記載した一つ一つのルート設定をroute recordと呼ぶ
  • ネストしたパスの場合は、複数の route record にマッチする場合がある
  • そのため、$routeオブジェクトは、matchedという Array プロパティを持ち、マッチした全ての Route 情報を保持している。
// Route設定
routes = [
  {
    path: '/foo',
    component: Foo,
    children: [
      {
        path: 'bar',
        component: Bar,
        // a meta field
        meta: { requiresAuth: true },
      },
    ],
  },
];

// 例えば上記の設定の場合、'/foo/bar'にアクセスすると、
// matched配列には2つのRouteオブジェクトが入る

// ナビゲーションガード
router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // this route requires auth, check if logged in
    // if not, redirect to login page.
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath },
      });
    } else {
      next();
    }
  } else {
    next(); // make sure to always call next()!
  }
});

# Transitions

TODO: Vue の Transition を読んでから

# Data Fetching

ルートの変更をトリガにデータを取得する場合の方法は 2 つある。詳細はこちら (opens new window)を参照。

# ナビゲーション後に取得

コンポーネントのライフサイクルメソッド(created)においてデータ取得

// ナビゲーション後に取得
MyComponent = {
  created() {
    this.fetchData();
  },
  watch: {
    // call again the method if the route changes
    $route: 'fetchData',
  },
  methods: {
    fetchData() {
      /* なんらかのデータ取得処理と、データのstateへの格納処理 */
    },
  },
};

# ナビゲーション前に取得

コンポーネントのbeforeRouteEnterガードで取得する

// ナビゲーション前に取得
MyComponent = {
  beforeRouteEnter(to, from, next) {
    /* なんらかのデータ取得処理 */
    next(vm => vm.setData(data));
  },
  beforeRouteUpdate(to, from, next) {
    /* なんらかのデータ取得処理 */
    this.setData(data));
    next();
  },
  methods: {
    setData(err, post) {
      /* データのstateへの格納処理 */
    },
  },
};

# Scroll Behavior

ページ遷移時のスクロール位置を設定できる。

const router = new VueRouter({
  scrollBehavior(to, from, savedPosition) {
    // return Position Object
  },
});

Position Object の形式は以下のいずれか。

  • { x: number, y: number }
  • { selector: string, offset? : { x: number, y: number }}
  • 上記 2 つのいずれかを返すPromise
scrollBehavior (to, from, savedPosition) {
  // 常にトップ位置にスクロール
  return { x: 0, y: 0 }

  // 以前の位置にスクロール、以前の位置がなければトップにスクロール
  if (savedPosition) {
    return savedPosition
  } else {
    return { x: 0, y: 0 }
  }

  // アンカーがある場合はその位置にスクロール
  if (to.hash) {
    return {
      selector: to.hash
      // , offset: { x: 0, y: 10 }
    }
  }
}

# Lazy Loading Routes

Async Component を作成するには、Promise を使う。

const Foo = () =>
  Promise.resolve({
    /* component definition */
  });

webpack では、importが使える。

import('./Foo.vue'); // returns a Promise

上記の 2 つの事実から、vue-router でコンポーネントを Lazy Loading するには次のようにする。

const Foo = () => import('./Foo.vue');

なお、Babel を使っている場合は、import 文を正しく理解させるため、syntax-dynamic-importをあわせて導入する必要がある。