# カテゴリー一覧を表示するVue.jsアプリの作成

2024-02-11 14:04:00

VuePressで書いた記事を、カテゴリーごとに一覧表示したかった。 enhanceApp.jsでMarkdownのfrontmatterからカテゴリーを集め、Category.vueでカテゴリーごとに記事を並べる。

# enhanceApp.jsで全ページのカテゴリーを集める

VuePressのenhanceApp.jsで、Markdownファイルからカテゴリーを取得する。 Vueのmixinを使い、全てのページで共通の処理として定義する。

export default ({ Vue }) => {
  Vue.mixin({
    computed: {
      categories() {
        const categories = new Set(); 

        this.$site.pages.forEach(page => {
          if (page.frontmatter.category) {
            categories.add(page.frontmatter.category);
          }
        });

        return Array.from(categories);
      }
    }
  });
};

このコードは、全てのページのfrontmatterから category を取り出す。 Set に入れることで、重複のないカテゴリー一覧を作っている。

# Category.vueでカテゴリーごとに記事を並べる

カテゴリーごとに記事一覧を表示するVueコンポーネントを、Category.vueとして作成する。

<template>
  <div>
    <div v-for="category in categories" :key="category">
      <h3>{{ category }} の記事一覧</h3>
      <ul>
        <li v-for="post in filteredPosts(category)" :key="post.title">
          <!-- post.title が存在する場合のみ表示 -->
          <router-link v-if="post.frontmatter.title" :to="post.path">{{ post.frontmatter.title }}</router-link>
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      categories: [], 
    };
  },
  computed: {
    filteredPosts() {
      return (category) => {
        if (!this.$site || !this.$site.pages) {
          return [];
        }

        return this.$site.pages
          .filter(page => page.frontmatter.category === category && page.frontmatter.title)
          .sort((a, b) => new Date(b.frontmatter.date) - new Date(a.frontmatter.date));
      };
    },
  },
  created() {
    this.categories = this.$store.state.categories;
  }
};
</script>

filteredPosts は、カテゴリーが一致し、タイトルがある記事だけを返す。 記事は date の新しい順に並べている。 カテゴリーは created フック内でVuexストアから取得している。

# カテゴリーをCategory.vue内に直接書く場合

カテゴリーを固定で定義したい場合は、data に直接書く。

<template>
  <div>
    <div v-for="category in categories" :key="category">
      <h3>{{ category }} の記事一覧</h3>
      <ul>
        <li v-for="post in filteredPosts(category)" :key="post.title">
          <!-- post.title が存在する場合のみ表示 -->
          <router-link v-if="post.frontmatter.title" :to="post.path">{{ post.frontmatter.title }}</router-link>
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      categories: ["Vue.js", "React", "Angular"], // カテゴリーを直接定義
    };
  },
  computed: {
    filteredPosts() {
      return (category) => {
        if (!this.$site || !this.$site.pages) {
          return [];
        }

        return this.$site.pages
          .filter(page => page.frontmatter.category === category && page.frontmatter.title)
          .sort((a, b) => new Date(b.frontmatter.date) - new Date(a.frontmatter.date));
      };
    },
  },
};
</script>

この例では ["Vue.js", "React", "Angular"] を定義している。 created フックでストアから取得する処理は不要になる。