jtk

【学習メモ】Vue.js入門 基礎から実践アプリケーション開発まで その3

4 Vue Router を活用したアプリケーション開発

4.2 ルーティングの基礎

4.2.2 ルーティング設計

ルートルーターコンストラク を用います。

<div id="app">
  <router-link to="/top">トップページ</router-link>
  <router-link to="/users">ユーザー一覧ページ</router-link>
  <router-view></router-view>
</div>
<script src="https://unpkg.com/vue@2.5.17"></script>
<script src="https://unpkg.com/vue-router@3.0.1"></script>
<script src="./assets/js/main.js"></script>
// ルーターコンストラクタ
// ルートオプションを渡してルーターインスタンスを生成
var router = new VueRouter({
  // ルート定義
  routes: [
    {
      path: "/top",
      component: {
        template: `<div>トップページです。</div>`
      }
    },
    {
      path: "/users",
      component: {
        template: `<div>ユーザー一覧ページです。</div>`
      }
    }
  ]
});

// Vue のマウント
// ルーターのインスタンスをrootとなるVueインスタンスに渡す
var app = new Vue({
  router: router,
  el: "#app"
});

4.4 サンプルアプリケーションの実装

<div id="app">
  <nav v-cloak>
    <!-- `router-link` によるナビゲーション定義 -->
    <router-link to="/top">トップページ</router-link>
    <router-link to="/users">ユーザー一覧ページ</router-link>
    <router-link to="/users/new?redirect=ture">新規ユーザー登録</router-link>
    <router-link to="/login" v-show="!Auth.loggedIn()">ログイン</router-link>
    <router-link to="/logout" v-show="Auth.loggedIn()">ログアウト</router-link>
  </nav>
  <router-view></router-view>
</div>
<script src="https://unpkg.com/vue@2.5.17"></script>
<script src="https://unpkg.com/vue-router@3.0.1"></script>

<!-- ユーザー一覧ページのテンプレート -->
<script type="text/x-template" id="user-list">
  <div>
    <div class="loading" v-if="loading">読み込み中</div>
    <div v-if="error" class="error">
      {{ error }}
    </div>
    <!-- usersがロードされたら各ユーザーの名前を表示する -->
    <div v-for="user in users" :key="user.id">
      <h2>{{ user.name }}</h2>
    </div>
  </div>
</script>

<!-- ユーザー詳細ページのテンプレート -->
<script type="text/x-template" id="user-detail">
  <div>
    <div class="loading" v-if="loading">読み込み中</div>
    <div v-if="error" class="error">
      {{ error }}
    </div>
    <!-- users がロードされたら各ユーザの名前を表示する -->
    <div v-if="user">
      <h2>{{ user.name }}</h2>
      <p>{{ user.description }}</p>
    </div>
  </div>
</script>

<!-- ユーザー作成ページのテンプレート -->
<script type="text/x-template" id="user-create">
  <div>
    <div class="sending" v-if="sending">Sending</div>
    <div>
      <h2>新規ユーザー作成</h2>
      <div>
        <label>名前: </label>
        <input type="text" v-model="user.name">
      </div>
      <div>
        <label>説明文: </label>
        <textarea v-model="user.description"></textarea>
      </div>
      <div v-if="error" class="error">
        {{ error }}
      </div>
      <div>
        <input type="button" @click="createUser" value="送信">
      </div>
    </div>
  </div>
</script>

<!-- ログインページのテンプレート -->
<script type="text/x-template" id="login">
  <div>
    <h2>Login</h2>
    <p v-if="$route.query.redirect">
      ログインしてください
    </p>
    <form @submit.prevent="login">
      <label><input v-model="email" placeholder="email"></label>
      <label><input v-model="pass" placeholder="password" type="password"></label>
      <br>
      <button type="submit">ログイン</button>
      <p v-if="error" class="error">ログインに失敗しました</p>
    </form>
  </div>
</script>

<script src="./assets/js/main.js"></script>
//////////////////////////
// ユーザー一覧
//////////////////////////
var getUsers = function(callback) {
  setTimeout(function() {
    callback(null, [
      {
        id: 1,
        name: "Takuya Tejima"
      },
      {
        id: 2,
        name: "Yohei Noda"
      }
    ]);
  }, 1000);
};

var UserList = {
  template: "#user-list",
  data: function() {
    return {
      loading: false,
      users: function() {
        return []; // 初期値の空配列
      },
      error: null
    };
  },
  // 初期化時にデータを取得する
  created: function() {
    this.fetchData();
  },
  // $route の変更をwatchすることでルーティングが変更されたときに再度データを取得
  watch: {
    $route: "fetchData"
  },
  methods: {
    fetchData: function() {
      this.loading = true;
      // 取得したデータの結果をusersに格納する
      getUsers(
        function(err, users) {
          this.loading = false;
          if (err) {
            this.error = err.toString();
          } else {
            this.users = users;
          }
        }.bind(this)
      );
    }
  }
};

//////////////////////////
// ユーザー詳細
//////////////////////////
var userData = [
  {
    id: 1,
    name: "Takuya Tejima",
    description: "東南アジアで働くエンジニアです。"
  },
  {
    id: 2,
    name: "Yohei Noda",
    description: "アウトドア・フットサルが趣味のエンジニアです。"
  }
];

var getUser = function(userId, callback) {
  setTimeout(function() {
    var filteredUsers = userData.filter(function(user) {
      return user.id === parseInt(userId, 10);
    });
    callback(null, filteredUsers && filteredUsers[0]);
  }, 1000);
};

var UserDetail = {
  template: "#user-detail",
  data: function() {
    return {
      loading: false,
      user: null,
      error: null
    };
  },
  // 初期化時にデータを取得する
  created: function() {
    this.fetchData();
  },
  // $route の変更をwatchすることでルーティングが変更されたときに再度データを取得
  watch: {
    $route: "fetchData"
  },
  methods: {
    fetchData: function() {
      this.loading = true;
      // `this.$route.params.userId` に現在のURL上のパラメータに対応した userIdが格納される
      getUser(
        this.$route.params.userId,
        function(err, user) {
          this.loading = false;
          if (err) {
            this.error = err.toString();
          } else {
            this.user = user;
          }
        }.bind(this)
      );
    }
  }
};

//////////////////////////
// ユーザー作成
//////////////////////////
// 擬似的にAPI経由で情報を更新したようにする
// 実際のWebアプリケーションではサーバーへへPOSTリクエストを行う
var postUser = function(params, callback) {
  setTimeout(function() {
    params.id = userData.length + 1;
    userData.push(params);
    callback(null, params);
  }, 1000);
};

// 新規ユーザー作成コンポーネント
var UserCreate = {
  template: "#user-create",
  data: function() {
    return {
      sending: false,
      user: this.defaultUser(),
      error: null
    };
  },
  created: function() {},
  methods: {
    defaultUser: function() {
      return {
        name: "",
        description: ""
      };
    },
    createUser: function() {
      // 入力パラメーターのバリデーション
      if (this.user.name.trim() === "") {
        this.error = "Nameは必須です";
        return;
      }
      if (this.user.description.trim() === "") {
        this.error = "Descriptionは必須です";
        return;
      }
      postUser(
        this.user,
        function(err, user) {
          this.sending = false;
          if (err) {
            this.error = null;
          } else {
            // デフォルトでフォームをリセット
            this.user = this.defaultUser();
            alert("新規ユーザーが登録されました");
            // ユーザー一覧ページに戻る
            this.$router.push("/users");
          }
        }.bind(this)
      );
    }
  }
};

//////////////////////////
// ログイン・ログアウト
//////////////////////////
var Auth = {
  login: function(email, pass, cb) {
    // ダミーデータを使った疑似ログイン
    setTimeout(function() {
      if (email === "vue@example.com" && pass === "vue") {
        // ログイン成功時はローカルストレージに taken を保存する
        localStorage.token = Math.random()
          .toString(36)
          .substring(7);
        if (cb) {
          cb(true);
        }
      } else {
        if (cb) {
          cb(false);
        }
      }
    }, 0);
  },
  logout: function() {
    delete localStorage.token;
  },
  loggedIn: function() {
    // ローカルストレージにtokenがあればログイン状態とみなす
    return !!localStorage.token;
  }
};

var Login = {
  template: "#login",
  data: function() {
    return {
      email: "vue@example.com",
      pass: "",
      error: false
    };
  },
  methods: {
    login: function() {
      Auth.login(
        this.email,
        this.pass,
        function(loggedIn) {
          if (!loggedIn) {
            this.error = true;
          } else {
            // redirect パラメータがついている場合はそのパスに遷移
            this.$router.replace(this.$route.query.redirect || "/");
          }
        }.bind(this)
      );
    }
  }
};

//////////////////////////
// ルーター設定
//////////////////////////
// ルートオプションを渡してルーターインスタンスを生成
var router = new VueRouter({
  routes: [
    {
      path: "/top",
      component: {
        template: `<div>トップページです。</div>`
      }
    },
    {
      path: "/users",
      component: UserList
    },
    {
      // ルー定義を追加
      path: "/users/new",
      component: UserCreate,
      beforeEnter: function(to, from, next) {
        // 認証されていない状態でアクセスした時はloginページに遷移する
        if (!Auth.loggedIn()) {
          next({
            path: "/login",
            query: { redirect: to.fullPath }
          });
        } else {
          // 認証済みであればそのまま新規ユーザー作成ページに進む
          next();
        }
      }
    },
    {
      path: "/users/:userId",
      component: UserDetail
    },
    {
      path: "/login",
      component: Login
    },
    {
      path: "/logout",
      beforeEnter: function(to, from, next) {
        Auth.logout();
        next("/top");
      }
    },
    {
      // 定義されていないパスへの対応。トップページへリダイレクトする。
      path: "*",
      redirect: "/top"
    }
  ]
});

//////////////////////////
// Vue のマウント
//////////////////////////
var app = new Vue({
  data: {
    Auth: Auth
  },
  router: router,
  el: "#app"
});