基礎からメモ: Vue.js CH9-p295 URLパラメータ付きの動的ページ
簡単な商品一覧ページのサンプルを作る。
src/views/Home.vue
とsrc/main.js
、src/App.vue
は前回と同じ。
まず商品一覧ページと商品詳細ページのコンポーネントを作る。
商品一覧ページ
[src/views/ProductList.vue]
<template> <div class="product-list"> <h1>商品一覧</h1> </div> </template>
商品詳細ページ
[src/views/Product.vue]
<template> <div class="product"> <h1>商品情報</h1> <p>このページは ID:{{ $route.params.id}} の詳細を表示する</p> </div> </template>
$route
は、マッチしたルートの情報が入ったオブジェクト。router.js
で定義した各ルートのURLを構成するパラメータなどの情報。
localhost:8080/#/product
にアクセスすると商品情報のページが、localhost:8080/#/product/1
などにアクセスすると「このページは ID:1 の詳細を表示する」という表示をする。
[src/router.js]
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '@/views/Home.vue' import ProductList from '@/views/ProductList.vue' import Product from '@/views/Product.vue' Vue.use(VueRouter) const router = new VueRouter({ routes: [ { path: '/', component: Home }, { path: '/product', component: ProductList }, { path: '/product/:id', component: Product } ] }) export default router
propsでパラメータをコンポーネントに渡す
コンポーネントに$route
を直接書くと、ルートとコンポーネントが密結合になるので避けたい。そこでパラメータはprops
として渡す。
[src/router.js]
......................... const router = new VueRouter({ routes: [ { path: '/', component: Home }, { path: '/product', component: ProductList }, { path: '/product/:id', component: Product, // 関数でpropsを定義、第一引数に現在のルートオブジェクトが渡る props: route => ({ id: Number(route.params.id) }) } ] }) ......................
Product.vue
を変更して、パラメータをprops
経由で渡す仕組みにする。
[src/views/Product.vue]
<template> <div class="product"> <h1>商品情報</h1> <p>このページは ID:{{ id }} の詳細を表示する</p> </div> </template> <script> export default { props: { id: Number } } </script>
この変更でも「このページは ID:1 の詳細を表示する」という形で、商品情報のページは同様に表示される。
コンテンツ表示
商品IDを表示するだけでは味気ないので、商品の詳細を書ける配列リストを作り、各商品情報にアクセスすると詳細を表示できる形にする。
商品情報のリストを定義した products.js を作り、ProductList.vueで読み込む。
[src/api/products.js]
// 商品リスト const database = [ { id: 1, name: '商品A', price: 100, content: '商品A詳細' }, { id: 2, name: '商品B', price: 200, content: '商品B詳細' }, { id: 3, name: '商品C', price: 300, content: '商品C詳細' } ] // インポート先で使用できる関数をオブジェクトとして定義 export default { fetch(id) { return database }, find(id) { return database.find(el => el.id === id) }, asyncFind(id, callback) { setTimeout(() => { callback(database.find(el => el.id === id)) }, 1000) } }
商品リスト一覧の表示
ProductList.vue は商品リストから一覧を表示して、各商品詳細へのリンクから Product.vue に遷移できる仕組みにする。
[src/views/ProductList.vue]
<template> <div class="product-list"> <h1>商品一覧</h1> <ul> <li v-for="{ id, name } in list" :key="id"> <router-link :to="`/product/${id}`">{{ name }}</router-link> </li> </ul> </div> </template> <script> import products from '@/api/products.js' export default { computed: { list: () => products.fetch() } } </script>
各商品情報の表示
Product.vue では、props
のid
を監視し、変更があれば非同期通信で商品詳細を取得し表示する。
[src/views/Product.vue]
<template> <div class="product" v-if="item" key="product"> <h1>商品情報</h1> <dl class="product-table"> <dt>商品名</dt><dd>{{ item.name }}</dd> <dt>価格</dt><dd>{{ item.price }}</dd> <dt>商品説明</dt><dd>{{ item.content }}</dd> </dl> </div> <div v-else key="loading">商品情報を読み込んでいます....</div> </template> <script> import products from '@/api/products.js' export default { props: { id: Number }, data() { return { item: null } }, watch: { id: { handler() { products.asyncFind(this.id, item => { this.item = item }) }, immediate: true // 初期読み込み時にも呼び出す } } } </script> <style> .product-table, .product-table * { margin: 0; padding: 0; box-sizing: border-box; } .product-table { display: flex; flex-wrap: wrap; margin: 20px; width: 230px; background-color: #FFF; border: 1px solid #ddd; } .product-table dt { width: 40%; padding: 8px 10px; background-color: #f2f2f2; border-bottom: 1px solid #ddd; font-weight: bold; } .product-table dd { width: 60%; padding: 8px 0 8px 22px; border-bottom: 1px solid #E4E4E4; } </style>
商品一覧のリストからいずれかの商品情報ページへのリンクをクリックすると、監視されたIDに変更が生じる。その際products.js
に定義されたasyncFind
関数を使って、非同期で商品情報を得る。asyncFind
関数は1000ミリ秒ごとに該当する商品情報を渡しコールバックを実行する。コールバック関数は渡された商品情報を自身のitem
データに格納し、商品情報用のテンプレートが描画される。
URLのパラメータとクエリの変更はコンポーネントのライフサイクル初期化を行わないので、watch
によるデータの監視という方法をとる必要がある。