コラム

[Vue3]Vuexの値更新時に発火するイベントsubscribe機能を使用する例

Vue3でVuexのsubscribe機能を使用する例の紹介としてアプリケーションを作成してみました。
この機能を使用するとmutations処理の終了を感知し、追加処理をする事が出来ます。

アプリケーション動作イメージ

このアプリケーションの要点は、本登録ボタン押下時にサーバに登録処理をかけます、登録処理終了後にVuexに保管している、新規登録商品に値をセットするので、そのタイミングで商品一覧のsubscribeメソッドが呼ばれ新規登録した商品を商品一覧に追加するというリアルタイムに商品一覧を更新できるという点です。

設計

画面部分

  • 商品一覧画面
    • サーバで管理されている商品一覧を表示する。
    • 新規商品登録時にはサーバ処理が終了後にsubscribeイベントが呼ばれ、新規登録商品を一覧に追加する。
  • 商品登録画面
    • 商品の仮登録、本登録処理を行う。仮登録商品はVuexで管理し当該画面の仮登録一覧に表示する。本登録押下時にはVuexの商品登録actionを呼び、商品一覧画面に遷移する。

Vuex部分

  • 仮登録商品state
    • 商品登録画面で仮登録された商品を管理する。
  • 登録状況state
    • 登録状況を管理する。
  • 新規登録商品state
    • SpringBootサーバへ商品登録依頼をして、返ってきた商品情報をセットする。
  • 商品登録action
    • SpringBootサーバへ仮登録商品stateの商品登録依頼をする。完了後、返ってきた商品情報を新規登録商品stateにセットする。

サーバ部分

商品一覧、商品登録RestAPIを提供するSpringBootサーバを用意する。
商品情報はメモリ上で管理する。

全体構造

  1. 商品一覧画面にVuexの登録状況を表示・バインドする。
  2. 商品一覧画面に商品情報を表示する為にSpringBootサーバからデータを取得する。
  3. 商品登録画面への遷移ボタンがある。
  4. 商品登録画面にVuexの仮登録商品を表示・バインドする。
  5. 商品登録画面で商品情報を入力し、仮登録ボタンを押下するとVuexの仮登録商品に追加される。
  6. 商品登録画面で登録ボタンを押下すると、Vuexの商品登録actionをコールする。
  7. Vuexの商品登録actionは仮登録商品を取得する。
  8. Vuexの商品登録actionは登録状況を登録中に更新する。
  9. Vuexの商品登録actionはSpringBootサーバに仮登録商品の登録処理を依頼する。
  10. 商品登録画面で登録ボタン押下時に商品一覧画面に遷移する。
  11. Vuexの商品登録actionはSpringBootサーバの登録処理が終了後、登録状況を登録完了に更新する。
  12. Vuexの商品登録actionはSpringBootサーバの登録処理が終了後、新規登録商品情報を更新する。
  13. 商品一覧画面で定義したsubscribeイベントが呼ばれる。
  14. 商品一覧画面でsubscribeに定義したメソッドで新規商品を商品一覧に追加する。

Vue.js実装

商品一覧画面

GoodsList.vue

<template>
			登録状況:{{ store.getters.getRegistStatus }}<br/><br/>
			<button v-on:click="moveRegistGoods">商品登録</button><button v-on:click="getGoods">表示更新</button>
			<br/><br/>
			<center>
		<div class="th-sticky_wrap">

			<table class="st-tbl1">
				<thead>
					<tr>
						<th>コード</th>
						<th>名称</th>
						<th>カテゴリ</th>
					</tr>
				</thead>
				<tbody>
					<tr v-for="(goods) in goodsList" v-bind:key="goods.code">
						<td>{{ goods.code}}</td>
						<td>{{ goods.name }}</td>
						<td>{{ goods.category }}</td>
					</tr>
				</tbody>
			</table>

		</div>
			</center>
		
</template>

<script>
	import axios from 'axios'
	import { ref, onMounted } from 'vue'
	import { useRouter  } from 'vue-router'
	import { useStore } from 'vuex'

export default {
	name: 'GoodsList',
	setup(){
		const router = useRouter()
		const store = useStore()
		const goodsList = ref(null)
		
		onMounted(() => {
			getGoods()
			
			//subscribeイベントが発火した際の処理を記述する。
			//商品一覧に新規登録商品を追加する
			store.subscribe((mutation, state) => {
				if (mutation.type === 'setregistedGoodsList') {
					state.registedGoodsList.forEach(element => goodsList.value.push( element ) );
				}
			})
		})
				
		const moveRegistGoods = () =>{
			router.push( "/regist" )
		}
		
		//商品をサーバから取得するメソッド
		const getGoods = () =>{
			axios.get('http://localhost:8080/goods/list')
				.then(response => {
					goodsList.value = response.data
					console.log( response.data );
			})
		}
		
		return{
			moveRegistGoods,
			getGoods,
			goodsList,
			store
		}
	}
	
}
</script>

商品登録画面

RegistGoods.vue

<template>
	<center>
	<div class="view-div">
		<h2>仮商品仮登録</h2>
		<center>
		<table>
			<tr>
				<td>商品コード</td>
				<td><input type="text" v-model="goods.code"></td>
			</tr>
			<tr>
				<td>商品名</td>
				<td><input type="text" v-model="goods.name"></td>
			</tr>
			<tr>
				<td>商品カテゴリ</td>
				<td><input type="text" v-model="goods.category"></td>
			</tr>
		</table>
		</center><br/>
		<button v-on:click="kariRegist">仮登録</button><button v-on:click="prev">戻る</button><br/><br/>
	</div>
	
	<div class="view-div">
		<h2>仮商品登録リスト</h2>
		<div class="th-sticky_wrap">

			<center>
				<table class="st-tbl1">
					<thead>
						<tr>
							<th>コード</th>
							<th>名称</th>
							<th>カテゴリ</th>
						</tr>
					</thead>
					<tbody>
						<tr v-for="(goods) in store.getters.getGoodsList" v-bind:key="goods.code">
							<td>{{ goods.code}}</td>
							<td>{{ goods.name }}</td>
							<td>{{ goods.category }}</td>
						</tr>
					</tbody>
				</table>
			</center><br/>
			<button v-on:click="regist">本登録</button><br/><br/>
		</div>
	</div>
	</center>
</template>
<script>
	import { useRouter  } from 'vue-router'
	import { useStore } from 'vuex'
	import { ref } from 'vue'

	export default {
		name: 'RegistGoods',
		
		setup(){
			const router = useRouter()
			const store = useStore()
			const goods = ref({code:'',name:'',category:''})
			
			//仮登録メソッド、VuexのkariRegistGoodsメソッドを呼ぶ。
			const kariRegist = ()=>{
				store.dispatch('kariRegistGoods', goods.value)
			}
			
			//本登録メソッド、VuexのregistGoodsメソッドを呼ぶ。
			//registGoods内ではサーバに対して登録処理を行う。
			const regist =()=>{
				store.dispatch('registGoods')
				router.push( "/" )
			}
			
			const prev = ()=>{
				router.push( "/" )
			}
		
			return{
				kariRegist,
				regist,
				prev,
				store,
				goods
			}
		}
	}
</script>

Vuex設定

index.js

import { createStore } from 'vuex'
import axios from 'axios'


export default createStore({
  state: {
    registStatus:'', //登録状況
    goodsList:[], //仮登録商品
    registedGoodsList:[] //新規登録商品
  },
  mutations: {
	
	//仮登録商品設定
	kariRegistGoods: function(state, value){
		let goods = {'code':value.code, 'name':value.name, 'category':value.category}
		state.goodsList.push( goods )
	},
	
	//仮登録商品クリア
	clearKariRegistGoods: function(state){
		state.goodsList = []
	},
	
	//登録状況設定
    setRegistStatus: function(state, value) {
      state.registStatus = value
    },
    
    //新規登録商品設定
    setRegistedGoodsList: function(state, value) {
      state.registedGoodsList = value
    },
  },
  actions: {
	
	//商品仮登録、stateのgoodsListに設定
	kariRegistGoods: function( { commit }, goods ){
		commit( "kariRegistGoods", goods );
	},

	//商品本登録、サーバに登録処理を依頼し返ってきた商品情報を新規登録商品として
	//stateのregistedGoodsListに設定
    registGoods: function({ commit }){

		commit('setRegistStatus', "処理中")
		
		axios.post('http://localhost:8080/goods/regist', this.state.goodsList)
			.then(response => {
			
			if( response.status == 200 ){
				commit('clearKariRegistGoods')
				commit('setRegistedGoodsList', response.data)
				commit('setRegistStatus', "登録完了")
			}else{
				commit('setRegistStatus', "登録エラー")
			}
		})
		
	}
  },
  modules: {
  },
  
  getters:{
	getGoodsList( state ){ return state.goodsList; },
	getRegistStatus( state ){ return state.registStatus; }
  
  }
})

Router設定

index.js

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'GoodsList',
    component: () => import('../views/GoodsList.vue')
  },
  {
    path: '/regist',
    name: 'RegistGoods',
    component: () => import('../views/RegistGoods.vue')
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

サーバ実装

RestApiコントローラー

GoodsController.java

package com.example.vuexmulti.ac;

import java.lang.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.vuexmulti.service.GoodsService;
import com.example.vuexmulti.service.Goods;

@CrossOrigin
@RestController
@RequestMapping("/goods")
public class GoodsController {

	@Autowired
	private GoodsService goodsService;
	
    /**
     * 商品情報リストを取得
     * @return
     */
    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public Goods[] getGoodsList() {
    	return goodsService.getGoods();
    }

	/**
	* 商品情報登録
	* @return 結果
	*/
	@RequestMapping(value = "/regist", method = RequestMethod.POST)
	public Goods[] regist( @RequestBody Goods[] goodsList ){
		
		//subscribeイベントの通知が分かりやすいように5秒間待機
		try{
			Thread.sleep(5000);
		}catch(Exception e){}
		
		for( Goods goods : goodsList ){
			System.out.println( goods.getCode() );
			System.out.println( goods.getName() );
			System.out.println( goods.getCategory() );
			
			goodsService.registGoods( goods );
			
		}
		return goodsList;
	}
}

商品管理サービス、エンティティ

Goods.java

package com.example.vuexmulti.service;

/**
 * 商品情報クラス
 */
public class Goods{
	private String code;
	private String name;
	private String category;
	
	public Goods(){
	}
	
	public Goods( String code, String name, String category){
		this.code = code;
		this.name = name;
		this.category = category;
	}

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
	
	public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }
}

GoodsService.java

登録した商品はgoodsListに格納する。今回は管理アプリケーションなのでDBは使用しない。

package com.example.vuexmulti.service;

import java.util.*;

import org.springframework.context.annotation.Scope;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import javax.annotation.*;


/**
* 商品の取得、追加を行うサービス
**/
@Scope("singleton")
@Service
public class GoodsService {
	
	private List<Goods> goodsList = new ArrayList<Goods>();
	
	/**
	 * 商品情報を取得するメソッド
	**/
	public Goods[] getGoods(){
		return goodsList.toArray( new Goods[]{} );
	}
	
	/**
	 * 商品情報を登録するメソッド
	**/
	public void registGoods( Goods goods ){
		goodsList.add( goods );
	}
	
}

以上、Vuexの値更新時に発火するイベントのsubscribe機能を使用してみました。
このような使い方をすると、登録処理終了後に再度ボタンを検索ボタンを押下しなくても、リストにデータが反映されよりリアルタイムにデータを見る事が出来るので便利だと思います。
他にも色々使用できる所はあるので、実践で使用していき機会があればまた紹介したいと思います。

今回使用したコードは此方から閲覧できます。

この記事をシェアする
  • Facebookアイコン
  • Twitterアイコン
  • LINEアイコン

お問い合わせ ITに関するお悩み不安が少しでもありましたら、
ぜひお気軽にお問い合わせください

お客様のお悩みや不安、課題などを丁寧に、そして誠実にお伺いいたします。

お問い合わせはこちら
お電話でのお問い合わせ 03-5820-1777(平日10:00〜18:00)
よくあるご質問