0
0

More than 3 years have passed since last update.

Vue.jsで非同期検索の実装。

Last updated at Posted at 2021-07-11

はじめに

オリジナルアプリを作成中、ストックしたレシピの検索を非同期で実装したいと思い挑戦。
その中でいくつか躓いた点があったため記録として残します。

以下が最終的な形です。
完成の動きはQiitaのストック一覧の検索を目指しました。
c1ed4252c1d1d32a04bad4dd96342256.gif

もくじ

1. 検索フォーム設置
2. リレーション関係であるデータの取得
3. 非同期検索機能の実装
4. aタグのリンク
5. 日付のformatの表示方法
6. レンダリングの並び順を変える

条件

macOS: "11.2.3 Big Sur"
Laravel Framework: "6.20.27"
PHP: "8.0.7"
nginx: "1.18"
mysql: "8.0"
vue: "2.6.14"

使用するファイル

note.blade.php ストック一覧のページ
AsyncSearch.vue 非同期の検索処理

1. 検索フォーム設置

検索に使用するフォームを作成します。

AsyncSearch.vue
<template>
  <div>
    <input type="text" class="search_note" v-model="keyword" placeholder="ノート内を検索">
  </div>

<!-- 一部省略 -->
</template>

<script>
  data() {
    return {
      keyword: '',
    }
  },
</script>

2. リレーション関係であるデータの取得

blade側からストックしたレシピのデータをAsyncSearch.vueへ渡します。

note.blade.php
<async-search
  :stock-recipes = '@json($recipes)' // ストックされたレシピ情報が格納
> 
</async-search>

vue側では、bladeから送られてきた、「ストックされている全レシピの情報の配列データ($recipes)」をv-forで1つずつ表示する処理を記述します。

AsyncSearch.vue
<template>
<!-- 一部省略 -->

  <div class="post-recipe-card" v-for="recipe in stockRecipes" :key="recipe.id">
    <h3 class="card-title"> {{ recipe.title }} </h3>
  </div>

<!-- 一部省略 -->
</template>


<script>
  export default {
  props: {
    StockRecipes: {
      type: Array,
      default: [],
    },
  },
  data() {
    return {
      keyword: '',
      stockRecipes: this.StockRecipes,
    }
  },
</script>

例えば、レシピのタイトルを表示させたい場合は、以下のように記述することで表示することができます。

AsyncSearch.vue
{{ recipe.title }}

ただ、レシピに紐づく作成者の名前タグなども上記のようなノリで書こうとしたら以下のエラーが...。

AsyncSearch.vue
<!-- レシピの作成者の名前を表示させたい -->
{{ recipe.user.name }}さん

<!-- Console上で以下のエラー -->
[Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"

どうしたものか...となった時に、同じ境遇の方がいました。

ということで、Controllerでデータの取得の記述を少し修正。
N+1問題をすでに解消しているならば、ここは問題ないかなと思います。

Controller
$recipes = $user->note->sortByDesc('created_at')
->load('user', 'stocks', 'tags', 'mealType', 'mealClass');

これでリレーション関係であるデータの表示も実装完了!

3. 非同期検索機能の実装

メインの実装!
computedに非同期検索の処理を記述します。

AsyncSearch.vue
<script>
  computed: {
    // フォームに入力された文字に該当するデータの表示
    filterRecipes() {
      let filtered = [];
      for (let i in this.stockRecipes) {
          let recipe = this.stockRecipes[i];
          if (
             recipe.title.indexOf(this.keyword) !== -1 ||
             recipe.meal_type.name.indexOf(this.keyword) !== -1 ||
             recipe.meal_class.name.indexOf(this.keyword) !== -1
           ) {
              filtered.push(recipe);
          }
        }
      return filtered;
    },
  },
</script>

if文で、各データに検索対象の文字が含まれていないかをチェックしています。
(以下は、titlemeal_type.namemeal_class.nameをチェックの対象としています。)

<script>
  if (
    recipe.title.indexOf(this.keyword) !== -1 ||
    recipe.meal_type.name.indexOf(this.keyword) !== -1 ||
    recipe.meal_class.name.indexOf(this.keyword) !== -1
  ) 
</script>

indexOfについて

非同期検索の記述は以下を参考にしました。

4. aタグのリンク

レシピのタイトルまたは画像をクリック → そのレシピの詳細画面に遷移
という実装がしたく記述方法に少し悩んでいたところ、またまた同じ境遇の方がいたためこちらを参考にしました。

・ URLをvue.js側で定義する。
・ Bladeでは、 {{ route('') }} で記述していたが、Vueではhttpを直接記述。
・ URL中にあるidなど、動的に変化するものは${}で囲む。

AsyncSearch.vue
<a :href="`http://localhost/recipes/${recipe.id}`">{{ recipe.title }}</a>

これで画面遷移の実装は完了!

5. 日付のformatの表示方法

現状

{{ recipe.created_at }}と記述しているため、以下のような日付の表記になっています。
スクリーンショット 2021-07-10 16.04.37.png
このままではどこか味気ないので、以下のように年月日表記にしたいところ...。
スクリーンショット 2021-07-10 16.28.06.png

bladeでは以下の記述で日付フォーマットを変更していましたが、vueではこの記述はできません。

blade
{{ $recipe->created_at->format('Y年m月d日') }}に投稿

探してみると、どうやらvue-momentというライブラリで変更できるそう。

早速、vue-momentをインストール!

npm install vue-moment

Vue.jsにインポート

AsyncSearch.vue
<script>
// vue-momentをインポート
import moment from "moment"

// 省略

</script>

ただ、これだけではフォーマットの変更はできないため以下のように
{{ recipe.created_at | createdDate }}
と記述することで、filtersに送られてきたデータを指定した日付のformatに変換して表示することができます。

AsyncSearch.vue
<template>

{{ recipe.created_at | createdDate }}に投稿

// 省略

</template>


<script>
import moment from "moment"

  // 日付のフォーマットを変更
  filters: {
    createdDate: function (date) {
      return moment(date).format("YYYY年MM月DD日");
    }
  },

// 省略

</script>

filtersについては以下を参考にしました。

6. レンダリングの並び順を変える

現状

v-forで表示されるものは昇順で表示されるため、最新のストックしたレシピが下に下に表示されるようになっています。
降順にしたい場合、computedで順番を入れ替えたデータを渡してあげることで解決できます。

AsyncSearch.vue
<script>
// 一部省略

computed: {
  reverseRecipes() {
      let filtered = [];
      for (let i in this.stockRecipes) {
          let recipe = this.stockRecipes[i];
          if (recipe.title.indexOf(this.keyword) !== -1 ||
          recipe.meal_type.name.indexOf(this.keyword) !== -1 ||
          recipe.meal_class.name.indexOf(this.keyword) !== -1) {
              filtered.push(recipe);
          }
        }
      return filtered.slice().reverse(); // 変更
  },
},
</script>

おわり

Vue.jsの基本的なところで躓いたところが多かったですが、Vue.jsの記述やデータの渡し方などかなり勉強になりました。
まだまだ記述は学習するところが多いので引き続き学習を続けます!

【参考】

実装にあたり以下のサイトも参考にさせて頂きました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0