はてだBlog(仮称)

私的なブログど真ん中のつもりでしたが、気づけばWebサイト系のアプリケーション開発周りで感じたこと寄りの自分メモなどをつれづれ述べています。2020年6月現在、Elasticsearch、pandas、CMSなどに関する話題が多めです。...ですが、だんだんとより私的なプログラムのスニペット置き場になりつつあります。ブログで述べている内容は所属組織で販売している製品などに関するものではなく、また所属する組織の見解を代表するものではありません。

Vue.js版ReactiveSearchのお試しコード例の2例目(カスタムクエリ型利用の場合にVuexを使って複数検索BOXまたがりる)- ElasticsearchのPoC

はじめに

itdepends.hateblo.jp

この記事はこちら↑の続きです。

冒頭のリンクのとおり、Elasticsearchをターゲットにした便利なSPAプラグインであるReactiveSearchを使ってみたのですが、ここではReactiveSearchにクエリを任せる標準方式ではなく、カスタムクエリ型で実現しました。

カスタムクエリ型の場合、Vue.js版のチュートリアルを流し読みしたのと私の英語読解力の範囲では、コンポーネントのプロパティ設定レベルでコンポーネント間で検索条件を共有するすることが難しいように見えました。 (ReactiveSearch自体のソースは読んでいない上の見解です。)

例えば、検索クエリはオリジナルなものにしたいが、加えて検索BOXを2つにしてそれぞれ別フィールドを検索する、また検索BOX1つめと2つめのAND(Elasticsearch クエリDSLで言うと、must)で検索したいというようなものをやるにはどうしたらというところです。

f:id:azotar:20190210133336p:plain

とは言え、Vue.jsに載っているアプリですので、このようなことも多少味付けすればできるハズなのは確実なのですが、念のため確かめてみました。

で、結論としては、Vue.jsのコンポーネント間の状態共有を使う方式一般で取り扱えそうです。

今回は、Vue.jsのステータス管理のプラグインであるVuexを付け足して使うことにしました。以下、その簡単な経緯とひとまず手元では動作したというサンプルコードです。

目次

まずは動かす

先に動かしてからの方が話がとおりやすいと思いますので、以下のコピペ手順で動かしてからちょいとだけ補足します。

なお、説明を簡単にするため、冒頭に述べた前回のものが動作している&環境の前提条件が整っているとします。

Vuexをインストール

例えばnpmでインストールしてください。

npm install vuex

Vue CLIでVuexが有効なプロジェクトをcreate

例えば、こんな感じでVue CLIを起動します。

vue create rs-sample-search-vuex

「Manually select features」を選択します。

次にこんな感じの画面が表示されるので、Vuexが対象になるように選択してください。

f:id:azotar:20190210135517p:plain

createしたプロジェクトのディレクトリのmain.js、store.js、App.vueを置き換え

それぞれ次の内容で置き換えて保存してください。

なお、時間があれば、置き換え前に、雛形になっているデフォルトのmain.js stone.jsを見ておくと、Vuexのお作法が見て取れます。(キーワードは、store、state、mutations、actionsあたりでしょうか。)

main.js

ReactiveSearchのお試しコードなので、ReactiveSearchをimportしたり「use」しているのはもちろん、「store」というところに注目ください。

import Vue from 'vue'
import App from './App.vue'
import store from './store'
import ReactiveSearch from "@appbaseio/reactivesearch-vue";

Vue.config.productionTip = false

Vue.use(ReactiveSearch);

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

store.js

検索BOXを2つ設けるとして、それぞれの現在の入力値を保持する(つもり)プロパティ(ここではkwsと名付けました)とそれらを操作するためのメソッドを定義しています。

ちょっとアレなコーディング&実装アーキテクチャかもしれませんが、ここでは、ReactiveSearchとVuexの噛み合わせを示すための例にしてあります*1

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    kws :{
      k1: "",
      k2: ""
    }
  },
  mutations: {
    mut_KW_UPDATE1(state,text){
      state.kws.k1 = text
    },
    mut_KW_UPDATE2(state,text){
      state.kws.k2 = text
    }
  },
  actions: {
    kw_update1(state,kw){
      state.commit("mut_KW_UPDATE1",kw);
    },
    kw_update2(state,kw){
      state.commit("mut_KW_UPDATE2",kw);
    }
  },
  getters:{
    kws(state){
      return state.kws;
    }
  }
})

App.vue

Vuexを使う&ここまでのおまじないで、「単一コンポーネント(App.vue)」の中で、「store」について次のことができるようになります。

  1. 「this.$store.dispatch」で、store.jsで定義されている「更新のためのメソッド」に引数を与えて、「store」のデータを更新できる。
  2. 「this.$store.getters」で、store.jsで定義されている「store」のデータを取得できる。
<template>
  <div id="app">
    <reactive-base app="rs_index" url="http://localhost:9200/" >
    <h5>検索お試しランチャー(Vuex)</h5>

    <data-search
        componentId="myDataSearch"
        placeholder="検索キーワード1"
        @valueChange="sb1ValueUpdate"
        :customQuery="myCustQuery"
        className="data-search"
    />

    <data-search
        componentId="myDataSearch2"
        placeholder="検索キーワード2"
        @valueChange="sb2ValueUpdate"
        :customQuery="myCustQuery"
        className="data-search"
    />
    <result-list
        componentId="mySearchResult"
        :react='{ or: ["myDataSearch","myDataSearch2"]  }'
        :renderData="getResultList"
        :from="0"
        :pages="5"
        :pagination="true"
     />
    </reactive-base>
  </div>
</template>

<script>
export default {
  name: "App",
  methods:{
    sb1ValueUpdate(value){ this.$store.dispatch('kw_update1',value) },
    sb2ValueUpdate(value){ this.$store.dispatch('kw_update2',value) },
    myCustQuery() {
       let kws_words = this.$store.getters.kws.k1 + " " + this.$store.getters.kws.k2 ;
       return { query:{ multi_match: { query: kws_words, fields:["title_ja","content_ja"] }}};
    },
    getResultList( data ) {
        return { title: data.title_ja,description:`<em>${data.title_ja}</em>` };
    }
  }
};
</script>

<style>
.data-search{ width: 800px; }
</style>

アプリを起動してブラウザでアクセス

例えば、次のように起動してください。

npm run start


手順ここまで。

今気づきましたが、ブラウザのコンソールにはワーニングが出まくっています。

また、操作によっては時々不思議な挙動*2をするもののひとまず動いています。

f:id:azotar:20190210135755p:plain

解説(もうちょっとだけ)

上記の単一コンポーネントApp.vueについて補足します。

  1. myCustQueryの返り値のJSONオブジェクトが、Elasticsearchに投げられるクエリDSLになります。ここでは今回の2BOXならではの検索クエリにはなっていませんが、例えば、この例のように、kws_wordsで検索ワードを与えることができていることは見て取れると思います。
  2. そのkws_wordsという文字列の変数は、storeから取得できています。
  3. storeは検索BOXの値を取得しているのですが、sb1ValueUpdate、sb2ValueUpdateという関数でアップデートされる仕組みになっています。
  4. sb1ValueUpdate、sb2ValueUpdateは、ReactiveSearchのdata-searchコンポーネント「@valueChange」でイベント関連付けされています。@valueChangeはそのコンポーネントvalueを指定された関数に引数として渡してくれるものです。要はこの検索BOXでいうと、検索BOXのテキストボックスに入力された値が変更されると、@valueChangeがReactiveSearchの仕組みで呼び出され、さらにsb1ValueUpdate、sb2...の対応するものが起動され、storeに保持される。というものになっています。

前回の例だと、customQueryで指定された関数の第一引数で、検索BOXのテキストボックスの値が受け取れるためこれを検索ワードに使っていましたが、今回は複数の検索BOXの値を使うため、storeを介して2つの検索BOXの値を取得するようにしています。

customQuery、@valueChangeその他についてはこちら↓ opensource.appbase.io ※こちらを見てこの記事で間違っているところがあれば是非是非ご指摘ください。

参考リンク

Vuex公式など

Vuexを特に予告なく登場させましたが、これらの公式についてリンクします。

vuex.vuejs.org

jp.vuejs.org

Vue.js Vuex、その他コーディングスタイルやアーキテクチャについて参考

本記事では記事を単純にするための私の構成力の都合で、いつもどおりアドホックな例に止まっていますが、コンポーネント間の通信についてはいろいろ考えどころがあります。 こちらについて、いくつか参考リンクを貼っておきます。

design.dena.com

medium.com

jp.vuejs.org

基礎から学ぶ Vue.js

*1:下手なコードの言い訳と本来はもう少し検討する必要があると思いますが、ここではそこまで述べていませんという意味です。

*2:不安定というよりは、あるコンポーネントが"こういう"条件の時に"こうなる"というReactiveSearchの仕様だと思いますが...