かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Vue.js で Router 使ってみよう

Router 使うと簡単に画面遷移するアプリ作れるってことで試してみましょう。 まぁ、Vue.js は公式ドキュメントがとてもよくて、さらには日本語もあって読みやすい!!なので、公式みたらわかるようになってるのですが、とりあえず試してみたログということで。

ちなみに Vue Router のドキュメントはこちら

router.vuejs.org

Router のないプロジェクトを Vue CLI で作ろう

とりあえずサクッと作ります。Router のない TypeScript のプロジェクトを作りました。とりあえず現状の最新の Vue CLI 3.9.2 でやってます。

$ vue create router-sample

オプションはこんな感じで

f:id:okazuki:20190706221228p:plain

Use class-style component syntax? は、No で行きます。それ以外は Enter 連打で。

Router を導入

ではプロジェクトが出来たので Router を導入していきます。プロジェクトのフォルダーで以下のコマンドでインストールします。

npm i vue-router

そして src/main.ts で Router を使うようにします。とりあえずの動作確認なので各種ページは適当に

import Vue from 'vue';
import Router, { RouteConfig } from 'vue-router';
import App from './App.vue';

Vue.use(Router); // これで vue-router を使うということを明示
Vue.config.productionTip = false;

// とりあえずのコンポーネント
const Home = Vue.extend({
  render() {
    return this.$createElement('h2', 'Home');
  },
});
const About = Vue.extend({
  render() {
    return this.$createElement('h2', 'About');
  },
});

// ルートを定義
const routes: RouteConfig[] = [
  { path: '/', component: Home },
  { path: '/about', component: About},
];
const router = new Router({
  routes,
});

new Vue({
  router,
  render: (h) => h(App),
}).$mount('#app');

そして、src/App.vue で Router 系のタグを入れます。

<template>
  <div id="app">
    <h1>Router sample</h1>
    <div>
      <router-link to='/'>Home</router-link>
      <router-link to='/about'>About</router-link>
    </div>
    <router-view></router-view>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  name: 'app',
});
</script>

f:id:okazuki:20190706224045p:plain

f:id:okazuki:20190706224109p:plain

ちゃんとしたコンポーネントにする

src/components フォルダーに Home.vueAbout.vue を追加して以下のようにします。

<template>
    <div>
        <h2>Home</h2>
        <router-link to="/about">About ページへ</router-link>
    </div>
</template>

<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
});
</script>
<template>
    <div>
        <h2>About</h2>
        <button @click="goBack">Go back</button>
    </div>
</template>

<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
    methods: {
        goBack(e: MouseEvent) {
            this.$router.back();
        },
    },
});
</script>

実行すると初期状態で Home が表示されて…

そして src/main.ts でこのコンポーネントを使うように変更します。

import Vue from 'vue';
import Router, { RouteConfig } from 'vue-router';
import App from './App.vue';
import Home from './components/Home.vue';
import About from './components/About.vue';

Vue.use(Router);
Vue.config.productionTip = false;

const routes: RouteConfig[] = [
  { path: '/', component: Home },
  { path: '/about', component: About},
];
const router = new Router({
  routes,
});

new Vue({
  router,
  render: (h) => h(App),
}).$mount('#app');

これでとりあえず動くはずです。

f:id:okazuki:20190706225258p:plain

f:id:okazuki:20190706225319p:plain

ページ内のリンクやボタンも動きます。コード補完も効いて書き味もばっちりです!!

ハッシュじゃない感じで

URL をみてみると http://localhost:8080/#/about みたいにハッシュが使われてます。これでもいいのですが HTML5 histry モードというのもあって URL が綺麗になります。適用の仕方は簡単で src/main.ts の Router をインスタンス化しているところで mode を history に設定するだけです。

以下のような感じで。

import Vue from 'vue';
import Router, { RouteConfig } from 'vue-router';
import App from './App.vue';
import Home from './components/Home.vue';
import About from './components/About.vue';

Vue.use(Router);
Vue.config.productionTip = false;

const routes: RouteConfig[] = [
  { path: '/', component: Home },
  { path: '/about', component: About},
];
const router = new Router({
  mode: 'history', // これを追加
  routes,
});

new Vue({
  router,
  render: (h) => h(App),
}).$mount('#app');

動かしてみると URL が綺麗になった!

f:id:okazuki:20190706232707p:plain

こうした時の注意点はデプロイ先で失敗したリクエストも全て index.html に回すするようにする必要があります。

URL にパラメーターも渡したい

http://localhost:8080/users/1 みたいにして 1 を渡したいってことがよくありますね。これもしてみましょう。

src/main.ts のルートの定義をちょっと追加します。

import Vue from 'vue';
import Router, { RouteConfig } from 'vue-router';
import App from './App.vue';
import Home from './components/Home.vue';
import About from './components/About.vue';
import Users from './components/Users.vue';

Vue.use(Router);
Vue.config.productionTip = false;

const routes: RouteConfig[] = [
  { path: '/', component: Home },
  { name: 'users', path: '/users/:id', component: Users},
  { path: '/about', component: About},
];
const router = new Router({
  mode: 'history', // これを追加
  routes,
});

new Vue({
  router,
  render: (h) => h(App),
}).$mount('#app');

/users/:id とパスを定義してるの点と、あとでプログラムからパラメーターを渡して画面遷移するために name を設定している点がポイントです。

src/components/Users.vue は以下のような感じです。

<template>
  <div>
    <h2>User</h2>
    <div v-if="loading">loading...</div>
    <div v-if="!loading">{{ id }}</div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
  data() {
    return {
        loading: false,
      id: 0,
    };
  },
  async created() {
      // 初回
      await this.fetchData();
  },
  async beforeRouteUpdate(to, from, next) {
      // users/1 から users/2 みたいにコンポーネントが再利用される時用
      await this.fetchData();
      next();
  },
  methods: {
      async fetchData() {
          this.loading = true;
          await new Promise((r) => {
            setInterval(r, 1000);
          });
          this.loading = false;
          this.id = parseInt(this.$route.params.id, 10);
      },
  },
});
</script>

これで http://localhost:8080/users/22 みたいな URL にアクセスすると以下のような表示になります。スクショにはとってませんが、ちゃんと 1 秒くらい loading... と表示されます。

f:id:okazuki:20190706234806p:plain

src/components/About.vue をちょっと変更してテキストに文字入れて users に画面遷移するようにしてみました。

<template>
    <div>
        <h2>About</h2>
        <button @click="goBack">Go back</button>

        <form @submit.prevent="submit">
            <input type="text" v-model="id" />
            <input type="submit" value="画面遷移" />
        </form>
    </div>
</template>

<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
    data() {
        return {
            id: '',
        };
    },
    methods: {
        goBack(e: MouseEvent) {
            this.$router.back();
        },
        submit() {
            this.$router.push({ name: 'users', params: { id: this.id }});
        },
    },
});
</script>

this.$router.push で簡単にいい感じに画面遷移出来ますね。素敵。

About を開いて適当に数字を入れて…

f:id:okazuki:20190707002502p:plain

画面遷移ボタンを押すか、Enter キーで users/:id で定義した Users コンポーネントに遷移します。

f:id:okazuki:20190707002600p:plain

おまけ

画面遷移と関係ないですが Home コンポーネントに入力した文字をそのまま大文字にして出すような処理も追加してみました。ルーター使っても普通に動いていいね。

<template>
  <div>
    <h2>Home</h2>
    <router-link to="/about">About ページへ</router-link>

    <div>
      <input type="text" v-model="input" />
    </div>
    <div>
      <span>{{ output }}</span>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
  data() {
    return {
      input: '',
      output: '',
    };
  },
  watch: {
    input(val: string) {
      this.output = val.toUpperCase();
    },
  },
});
</script>

まとめ

簡単でいい感じですね!!

試した時のソースコードなので整理されてないですが GitHub に上げてます。

github.com