ちょい足しVueでWordPressをプチSPA化
この記事はこの記事はVue Advent Calendar 2022の21日目の記事です。
非Jamstackで、普通のMPA構成のWordPressの一部にVue.js(以下Vue)を導入してプチSPA化した話です。なお、「ちょい足しVue」なる概念は拙筆『HTMLにちょい足しでできる! Vue.jsでサクッと動きをつける方法』にて解説しています。
記事一覧ページをSPA化
具体的には記事一覧ページをVueとWordPress Rest APIを使ってSPA化しました。通常だと次ページに移動した時やカテゴリー・タグを切り替えた時にページ遷移をはさんで内容が更新されますが、SPA化することでシームレスに切り替わるようにしました。
ちょい足しVueの面白いところはWordPressのようなPHPが動的にHTMLを生成するサイトでも後付けでVueの機能を拡張できるところです。ほかにもWordPress関数から出力されたものをHTMLを通じてVue側へ渡すといったややアクロバティックなことも可能です。ちなみに今回のデモページでもWordPressとVueの融合を行っています。
デモページ(内容はダミーです)
仕組みを解説
かんたんにですが、仕組みについて解説します。
マウント時
まず、マウント時に全てのカテゴリーとタグのデータと、記事データをAPIから取得し格納します。
/**
* すべてのカテゴリーをフェッチして、格納します
*/
const fetchAndStoreCategoryList = async () => {
const result = await fetch(`/wp-json/wp/v2/categories`);
categories.value = await result.json();
};
/**
* すべてのタグをフェッチして、格納します
*/
const fetchAndStoreTagList = async () => {
const result = await fetch(`/wp-json/wp/v2/tags`);
tags.value = await result.json();
};
/**
* 記事データをフェッチして格納します。クエリがある場合はクエリに基づいた絞り込みを行います
*/
const fetchAndStoreArticle = async () => {
let result;
if (query.value) {
result = await fetch(
`/wp-json/wp/v2/posts/?${query.value}&per_page=${postPerPage}&page=${currentPage.value}`
);
} else {
result = await fetch(
`/wp-json/wp/v2/posts/?per_page=${postPerPage}&page=${currentPage.value}`
);
}
maxPage.value = Number(result.headers.get("X-WP-TotalPages"));
articles.value = await result.json();
};
onMounted(async () => {
// カテゴリーとタグを取得
await fetchAndStoreCategoryList();
await fetchAndStoreTagList();
// 最新10件を取得
fetchAndStoreArticle();
});
カテゴリーとタグは下記URLを叩くと取得できます。
- /wp-json/wp/v2/categories
- /wp-json/wp/v2/tags
記事データは下記URLにで取得できます。
- /wp-json/wp/v2/posts/
カテゴリーなどの適切なクエリパラメータを付与することで絞りんだ記事データを得られ、per_page=10
とすれば10件分得られます。currentPage=2
とすれば10件ずつ分割した2ページ目の10件のデータが得られます。
currentPage
には初期値として1
、postPerPageはdata-postperpage
属性を持った要素に生やした値から取得しています。この要素ですが、data属性の値はget_option( 'posts_per_page' );
で取得されたものを出力しています。
<?php
$posts_per_page = get_option( 'posts_per_page' );
?>
<span data-postperpage="<?php echo $posts_per_page ?>"></span>
つまりWordPressダッシュボードで設定した1ページあたりの記事数を出力しているので、この要素を通じて間接的にVueと連携しています。今回は10
件で設定してます。
1ページあたりの記事数はデフォルトのAPIにはないので、このようなハイブリッドな構成になっています。(APIを独自拡張すれば、カテゴリーなどど同じようにRest APIからも取得できます)
また、最大で何ページ分あるかはレスポンスヘッダーのX-WP-TotalPages
にあるのでそれも格納しています。例えば該当記事が全部で24件あった時、1ページあたり10件の設定なら、X-WP-TotalPages
は3
になります。
描画
マウント時に取得したデータをv-for="category in categories"
などで回してラジオボタンを表示しています。しかし、絞り込みをしない「すべて」の選択肢はAPIから取得したものがないので、HTMLに直接書いています。
<p class="category">
カテゴリー: <br />
<span class="selector">
<label>
<input
type="radio"
name="category"
value="all"
v-model="selectedCategory"
checked
@change="fetchByFilter"
/>すべて
</label>
<label v-for="category in categories">
<input
type="radio"
name="category"
:value="category.id"
v-model="selectedCategory"
@change="fetchByFilter"
/>{{category.name}}
</label>
</span>
</p>
/** 選択されたカテゴリー */
const selectedCategory = ref("all");
カテゴリーのIDをラジオボタンの値として、v-modelでselectedCategory
のリアクティブな値として同期しています。値に変更があると記事の絞り込みを行う関数fetchByFilter
を呼び出します(後述)。
初期値として"all"
をいれています。
同様に取得した記事データもv-for
で回していて、記事データのオブジェクト内のプロパティに応じて出力していきます。
<ul class="articles" v-if="articles">
<li v-for="article in articles" :key="article.id" class="article">
<a :href="article.link" class="article_link">
<p class="article_category">
{{convertCategoryIdToName(article.categories[0])}}
</p>
<p class="article_tag">{{convertTagIdToName(article.tags[0])}}</p>
<h2 class="article_title">{{article.title.rendered}}</h2>
</a>
</li>
</ul>
少し注意が必要なのが記事データのオブジェクト内に記録されているカテゴリーやタグがIDで保存されているので、そのままではカテゴリーやタグ名を表示できません。そのためマウント時に取得したカテゴリーデータをもとに、IDから名称に変換する関数を用意しています。
/**
* カテゴリーIDからカテゴリー名に変換します
* @param {string} id
* @returns string カテゴリー名
*/
const convertCategoryIdToName = (id) =>
categories.value.find((category) => category.id === id).name;
表示するときにはこの関数を通しています。またカテゴリーは複数設定できるので配列で保存されています。今回は1つのみを表示するので配列1つ目を指定しています。
記事一覧をアップデートする
最初に10件の最新記事を取得しましたが、次の10件を取得したい場合は再度APIを叩きます。
<button @click="fetchPreviousPage" :disabled="currentPage === 1">Prev</button>
<button @click="fetchNextPage" :disabled="currentPage === maxPage">Next</button>
/**
* 次のページのデータをフェッチします
* @returns void
*/
const fetchNextPage = async () => {
if (currentPage.value === maxPage.value) {
return;
}
currentPage.value++;
await fetchAndStoreArticle();
};
currentPage.value
の値を1つ増やして取得&格納します。現在の値がmaxPaage.value
と同じときはAPIを叩きません。前のページに戻るボタンも同様です。
絞り込みを変更した場合も再度フェッチします。絞り込みの場合はAPIにクエリ付きで投げます。クエリは絞り込み選択状況によって変わりますが、それをcomputed
で作成しています。
/**
* 絞り込みの選択状態に応じてクエリを作成します
*/
const query = computed(() => {
if (selectedCategory.value === "all" && selectedTag.value === "all") {
return null;
} else if (selectedCategory.value !== "all" && selectedTag.value === "all") {
return `categories=${selectedCategory.value}`;
} else if (selectedCategory.value === "all" && selectedTag.value !== "all") {
return `tags=${selectedTag.value}`;
} else {
return `categories=${selectedCategory.value}&tags=${selectedTag.value}`;
}
});
カテゴリーとタグがいずれも"all"
の場合はnull
を返します。それ以外の場合は選択されているものに応じたクエリ文字列を返します。
const fetchAndStoreArticle = async () => {
let result;
if (query.value) {
result = await fetch(
`/wp-json/wp/v2/posts/?${query.value}&per_page=${postPerPage}&page=${currentPage.value}`
);
}
フェッチの関数にクエリがある場合(null
でない場合)にクエリつきのURLを叩いています。@change
で呼び出すfetchByFilter
関数はcurrentPage
を1
に戻してAPIを呼び出します
/**
* カテゴリーやタグに基づいた絞り込んでフェッチします
*/
const fetchByFilter = async () => {
currentPage.value = 1;
await fetchAndStoreArticle();
};
これにて記事の絞り込みやページ送りができるようになりました。
以上で記事一覧ページをVueでSPA化した簡単な流れになります。
プチSPA化のメリット
わざわざVueを個別に導入せず、はじめからヘッドレスWordPressでJamstackで作れば良いと思うかもしれませんが、いろいろな都合でそうなれるとも限りません。Rest APIを叩いてプチSPA化すること自体はバニラJavaScriptでも可能ですが、ページの状態管理などを自前で作ろうとすると手間がかかります。ピンポイントでも導入できるVueを使えばずっと楽に作れます。
特に記事一覧ページを通常のWordPress関数だけで作ろうとすると、カテゴリー・タグの切り替えやページ送りの度にページ遷移を挟むので体感で遅くなりがちです。WordPress Rest APIのほうが一般的にはレスポンスも早いのでサクッとした動作が実現できます。
まとめ
別のPHPやJSPといったフレームワークやフレームワークを使わない素のHTMLでもあまりケンカすることなく導入できるのが「ちょい足しVue」の特徴です。既にあるのものにVueで拡張することも可能です。ちょい足しVueという開発の選択肢としても検討してみはいかがでしょうか?
Discussion