【Vue.js】QiitaのAPIを使ったリアルタイム検索プログラムの作成方法

#【Vue.js】QiitaのAPIを使ったリアルタイム検索プログラムの作成方法

任意の検索KWを入力すると、該当するQiitaの最新２０記事のタイトル、リンク、LGTM数を一覧で表示するプログラムの作成方法。

何かをDLしたりインストールする必要がない。ファイルもHTMLファイルとjsファイルの２つだけあれば作成可能。


▼完成図（ブラウザの表示例）

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/783ab35e-2771-9ac2-f578-580b9dc96b99.png)|
|:--|


##目次
1. [使用するAPI](#使用するapi)
2. [実装する機能](#実装する機能)
3. [利用するソフトウェア](#利用するソフトウェア)
4. [ソフトウェアの読み込み](#ソフトウェアの読み込み)
5. [オプションの用意](#オプションの用意)
6. [dataオプションの準備](#dataオプションの準備)
7. [methodsオプションの準備](#methodsオプションの準備)
    1. [APIデータ取得条件の設定](#apiデータ取得条件の設定)
    2. [axiosの取得条件の設定](#axiosの取得条件の設定)
8. [createdオプションの準備](#createdオプションの準備)
    1. [createdオプションとは？（Vueライフサイクルの理解）](#createdオプションとはvueライフサイクルの理解)
    2. [createdの記述](#createdの記述)
    3. [lodashとは？](#lodashとは)
    4. [debounceとは？](#debounceとは)
9. [テンプレートの作成](#テンプレートの作成)
10. [watchオプションの作成](#watchオプションの作成)
11. [完成版](#完成版)



##実装する機能
- 検索窓の設置。
- 入力KWの変化を自動で検知し、自動検索を実行する。
- 自動検索まで６秒間待機する（リクエスト上限があるため）
- KWに該当する最新記事のtitle, リンク(URL), LGTMの一覧を表示
- KW入力中は「Waitting 6 seconds for your typping...」を表示
- Loading中は「Loading...」を表示
- 検索窓が空白の場合は自動検索を実行しない。



##使用するAPI
・API： Qiita API v2
・公式ページ：https://qiita.com/api/v2/docs
・API詳細：記事の一覧を更新日時の降順で返すAPI（GET item）
- 説明：https://qiita.com/api/v2/docs#get-apiv2items
- APIエンドポイント：https://qiita.com/api/v2/items


▼APIの種類は下記ページがわかりやすい。
https://qiita.com/tag1216/items/b0b90e30c7e581aa2b00


＜注意点＞
リクエスト回数制限あり
・認証しているユーザー：１時間に1000回まで
・非認証ユーザー：　１時間に60回まで





##利用するソフトウェア
1. Vue.js
2. Qiita API v2
3. HTTPクライアント　axios
4. ユーティリティライブラリ lodash

- axios: 指定したURLのデータを取得するツール
- lodash: キーボード入力完了まで処理を待機するツールdebounceのライブラリ


##ソフトウェアの読み込み
htmlファイルのbodyタグ上にCDNのURLを貼り付け。

```html:.html
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.19/lodash.min.js"></script>
```


##オプションの用意
jsファイルで使用するオプションは５つ。

1. el
2. data
3. watch
4. created
5. methods


```js:空のオプションを準備
var app1 = new Vue({
    el: '#app',
    data: {

    },
    watch:{

    },
    //HTTPリクエスト用。mountedでも可。DOMにアクセスする必要がなければcreatedの方が早い
    created: function(){

    },
    methods:{

    }
})
```


##dataオプションの準備
3つのプロパティを準備する。

1. items: APIから取得した検索結果を格納
2. keyword: 検索クエリ
3. message: ユーザーへの表示内容

```js:dataオプション
data: {
        items: null,
        keyword: '',
        message: ''
    }
```


##methodsオプションの準備
1. axiosでAPIを叩きデータを取得するメソッド
2. 検索クエリが未入力の場合はAPIを叩かない（処理終了）
3. エラー発生時の処理
4. データロード時の表示（Loading...）


```js:methodオプション
methods:{
        getAnswer: function(){
            //検索クエリが空の場合は処理終了
            if(this.keyword === ''){
                this.items = null
                //２回目以降の検索時にメッセージを空にする
                this.message = ""
                return 
            }

            //ロード中の表示
            vm.message = 'Loading...'

            //Vueインスタンスを格納する変数を定義
            var vm = this
            
            //リクエスト条件を定義。1ページ表示、表示件数は20件、検索クエリはキーワードの入力値
            var params = {page:3, per_page:20, query: this.keyword}
            
            //axiosのgetメソッドでAPIをたたく
            axios.get('https://qiita.com/api/v2/items', params)
                .then(function(res){
                    console.log(res)　//データ取得できたか確認用
                    this.item = res.data //itemプロパティに取得データを格納


                })
            //エラー発生時の処理
            .catch(function(error){
                vm.message = 'Error!' + error
            })

            //通信が完了したらLoadingの表示をクリア
            .finally(function(){
                vm.message = ''
            })
        }

    }
```

###APIデータ取得条件の設定
APIデータ要求。必要な取得条件はQiita APIの公式ページに記載されている。
`axios.get('URL', 取得条件`)

取得データは「.then()」内で「functionの引数」の値に入る。
`.then(function(res){処理})`


####APIの取得条件の確認
公式ページに記載されている３つのパラメータを記載する。
https://qiita.com/api/v2/docs#get-apiv2items

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/441029fd-ab0a-8a99-f065-4846b2e3bcbe.png)  |
|:-:|

(1)page, (2)per_page, (3)queryが必要となる。


####axiosの取得条件の設定
取得条件は①パラメータで直接URLに付与するか、②引数で指定することができる。

①パラメータで直接URLに付与
`axios.get('https://qiita.com/api/v2/items?page=1&per_page=20&query=javascript')`

②引数で指定
`axios.get('https://qiita.com/api/v2/items', {page:3, per_page:20, query: this.keyword})`

②'引数を変数に代入
`axios.get('https://qiita.com/api/v2/items', {params})`
　※変数に波カッコをつける。ないと指定がない状態になってしまう。



<br>
##createdオプションの準備
###createdオプションとは？（Vueライフサイクルの理解）
jsファイル読み込み時に初期段階で実行する。
Vueインスタンス作成時のライフサイクルで、elを読み込む前の段階で実行される。

PHPなどのコンストラクターのようなイメージ。

もし、DOMを読み込む必要があれば、createdの代わりにmountedを使用する。

[Vue公式ページのライフサイクル図](https://jp.vuejs.org/v2/guide/instance.html#%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%83%80%E3%82%A4%E3%82%A2%E3%82%B0%E3%83%A9%E3%83%A0)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/c65abbea-cd78-ee4b-0eb1-ce4c94dfa868.png)



###createdの記述
＜デバッグ用＞
1. 検索クエリのセット
2. APIを叩く(getAnswer)メソッドを実行

```js:.js
    //初期実行動作。el読み込み前のタイミングで以下処理を実行する。
    created: function(){
        this.keyword = 'JavaScript'
        this.getAnswer()
```

＜本番用＞
上記２つをコメントアウトし、lodashライブラリのdebounce関数を設定する。

指定時間経過後に、APIを叩くgetAnswerメソッドを実行するプログラム。

getAnswerは検索ボックスが空白の時、if文で実行しないようになっている。（初回ロード時は何も発生しない）


```js:.js
   //初期実行動作。el読み込み前のタイミングで以下処理を実行する。
    created: function(){
        // this.keyword = 'JavaScript'
        // this.getAnswer()
        this.debouncedGetAnswer = _.debounce(this.getAnswer, 6000)
```



###lodashとは
JavaScriptの便利な関数をまとめたライブラリ。
[lodashの機能一覧](https://lodash.com/docs/4.17.15)

lodashはVue同様CDNを読み込めば使える。

```:CDN
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.19/lodash.min.js"></script>
```


###debounceとは
lodashに用意された関数の一つ。指定秒数経過後に、指定した関数を実行する。
キーボード入力が終わるまで待つ場合などに使える。

公式ページ：https://lodash.com/docs/4.17.15#debounce

`_.debounce(func, ミリ秒)`
　┗ デフォルトはミリ秒 = 0




<br>
##テンプレートの作成
1. キーワード入力欄（v-modelで双方向バインディング）
2. Loadingメッセージ
3. 取得データ表示（タイトル名、リンク、LGTM数）

```html:.html
    <div id="app">
        <p>
            <input type="text" v-model="keyword">
            <button v-on:click="getAnswer()">検索</button>
        </p>
        <p>
            {{ message }}
        </p>
        <ul>
            <li v-for="item in items">
                <!-- {{ item.title }}  -->
                <a v-bind:href=" item.url " target="_blank">{{ item.title }} </a> LGTM:{{ item.likes_count}}
            </li>
        </ul>
    </div>
```


<br>
##watchオプションの作成
検索ボックスに新しいキーワードが入力されたら、表示を自動更新する。

1. タイピング中に表示するメッセージの作成
2. タイピング終了まで待機し、終了後にAPIへのリクエストメソッドを実行





```js:watchオプション
 watch:{
        keyword: function(newKW, oldKW){
            //文字入力中にメッセージを表示
            this.message = 'Waitting 6 seconds for your typping...'
            //作成したgetAnswerメソッドを実行する
            this.debouncedGetAnswer()
        }
```

リクエスト回数上限があるため、６秒間待機するよう設計している。





<br>
##完成版

```js:.js
var app1 = new Vue({
    el: '#app',
    data: {
        items: null,
        keyword: '',
        message: ''
    },
    watch:{
        keyword: function(newKW, oldKW){
            //文字入力中にメッセージを表示
            this.message = 'Waitting 6 seconds for your typping...'
            //作成したgetAnswerメソッドを実行する
            this.debouncedGetAnswer()
        }

    },
    //初期実行動作。el読み込み前のタイミングで以下処理を実行する。
    created: function(){
        // this.keyword = 'JavaScript'
        // this.getAnswer()
        this.debouncedGetAnswer = _.debounce(this.getAnswer, 6000)
        

    },
    methods:{
        getAnswer: function(){
            //検索クエリが空の場合は処理終了
            if(this.keyword === ''){
                this.items = null
                this.message = ""
                return 
            }

            //ロード中の表示
            this.message = 'Loading...'

            //Vueインスタンスを格納する変数を定義
            var vm = this
            
            //リクエスト条件を定義。1ページ表示、表示件数は20件、検索クエリはキーワードの入力値
            var params = {page:3, per_page:20, query:this.keyword}
            
            //axiosのgetメソッドでAPIをたたく
            axios.get('https://qiita.com/api/v2/items', {params})
            // axios.get('https://qiita.com/api/v2/items', {page:3, per_page:20, query: this.keyword})
            // axios.get('https://qiita.com/api/v2/items?page=1&per_page=20&query=javascript')
                .then(function(res){
                    console.log(res)　//データ取得できたか確認用
                    console.log(res.data)
                    console.log(vm.keyword)
                    vm.items = res.data //itemプロパティに取得データを格納　※thisはNG
                })
            //エラー発生時の処理
            .catch(function(error){
                vm.message = 'Error!' + error
            })

            //通信が完了したらLoadingの表示をクリア
            .finally(function(){
                vm.message = ''
            })
        }

    }
})
```

```html:.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Qiita API</title>
</head>
<body>
    <div id="app">
        <p>
            <input type="text" v-model="keyword">
            <button v-on:click="getAnswer()">検索</button>
        </p>
        <p>
            {{ message }}
        </p>
        <ul>
            <li v-for="item in items">
                <!-- {{ item.title }}  -->
                <a v-bind:href=" item.url " target="_blank">{{ item.title }} </a> LGTM:{{ item.likes_count}}
            </li>
        </ul>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.19/lodash.min.js"></script>
    <script src="js/main.js"></script>
</body>
</html>
```


<br>
以上。