コラム

[Vue.js]Composition Apiで画面作成(画面作成)

今回はVue.jsのバージョン3で使用可能なComposition Apiを使用して一覧、登録画面を作成してみたいと思います。
他の同じ形式の画面を作成する際のベースにも使えると思います。

サーバ側も簡単なアプリケーションをSpringBootで作成し、Ajax通信を行っています。
今回は画面側の説明、次回はサーバ側の説明をしていきたいと思います。

画面説明

機能一覧

画面機能
商品一覧一覧表示
検索
ソート
商品編集・登録登録編集
入力チェック

動作イメージ

Vue.jsプロジェクト作成

プロジェクト作成コマンド

//プロジェクト作成
vue create [プロジェクト名]

プロジェクトタイプ選択(Manually select featuresを選択)

//プロジェクトタイプ選択 Manually select featuresを選択
? Please pick a preset:
  router-sample ([Vue 2] babel, router, eslint)
  aa ([Vue 3] babel, eslint)
  Default ([Vue 2] babel, eslint)
  Default (Vue 3) ([Vue 3] babel, eslint)
> Manually select features

プロジェクトの特徴を選択(RouterとVuexを追加。TypeScriptは今回使用してません)

? Check the features needed for your project:
 (*) Choose Vue version
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 (*) Router
>(*) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

構成

ディレクトリ構成

プロジェクト名/
           ├─ src/
           ├─ App.vue ←編集
           ├─ main.js
           ├─ api/ ←追加
                      ├─ checker.js
                      ├─ serveraccess.js
           ├─ composable/ ←追加
                      ├─ GoodsRepository.js
                      ├─ GoodsFilter.js
                      ├─ GoodsSort.js
                      ├─ GoodsValidator.js
           ├─ router/
                      ├─ index.js ←編集
           ├─ store/
                      ├─ index.js ←編集
           ├─ views/
                      ├─ GoodsList ←追加
                      ├─ EditGoods ←追加

構成図

モジュール機能説明
App.vueメインのモジュール。router-viewタグを記述
RouterVue.js提供の画面ルーティング機能
VuexVue.js提供の状態共有機能
GoodsList.vue商品リスト画面用コンポーネント
EditGoods.vue商品登録、編集画面用コンポーネント
GoodsFilter.js商品名検索用モジュール
GoodsSort.js商品リストソート用モジュール
GoodsRepository.js商品取得、登録、編集用モジュール
GoodsValidator.js商品登録入力チェックモジュール
serveraccess.jsサーバへのアクセスモジュール
checker.js値チェック用モジュール

実装

実際のコードはにアップしてあります。

App.vue

<template>
  <router-view/>
</template>

<style>
 省略
</style>

商品一覧画面

・GoodsList.vue

商品リスト画面用コンポーネント

<template>
  <div style="text-align:left;">
		<input type="text" v-model="searchQuery">
		<div class="table">
			<table>
				<thead>
					<tr>
						<th @click="sort('code')">コード</th>
						<th @click="sort('name')">名前</th>
						<th @click="sort('category')">カテゴリ</th>
						<th @click="sort('price')">価格</th>
						<th></th>
					</tr>
				</thead>
				<tbody>
					<tr v-for="(data) in goods" :key="data.code">
						<td>{{ data.code }}</td>
						<td class="tdname">{{ data.name }}</td>
						<td>{{ categoryDatas.filter( function (value){ return value.id === data.category })[0].name }}</td>
						<td>{{ data.price }}</td>
						<td><router-link :to="{ name: 'editGoods', params:{id:data.code}}">編集</router-link></td>
					</tr>
				</tbody>
			</table>
		</div>
		<input type="button" @click="addGoods" value="新規登録">
  </div>
</template>

<script>

import { onUnmounted } from 'vue'
import goodsRepository from '../composables/GoodsRepository'
import goodsFilter from '../composables/GoodsFilter'
import goodsSort from '../composables/GoodsSort'
import { useRouter  } from 'vue-router'
import { useStore } from 'vuex'

export default ({
  name: 'GoodsList',

  setup(){
    //GoodsRepositoryモジュールからの変数、メソッドを宣言
  //カテゴリ用データ、商品リスト、商品取得メソッド
    const { categoryDatas, goodsList, getGoods} = goodsRepository()

  //GoodsFilterモジュールからの変数、メソッドを宣言
    //検索のマッチした商品リスト、検索文字列
    const { matchGoods, searchQuery } = goodsFilter( goodsList )

  //GoodsSortモジュールからの変数、メソッドを宣言
  //ソート用メソッド
    const { sort } = goodsSort( goodsList )
    
    //ルータ使用変数宣言
    const router = useRouter()

    //Vuex使用変数宣言
    const store = useStore()
    
    //新規作成ボタン押下時の処理
    const addGoods = () => {
      router.push("/editGoods")
    }

    //データ取得時のコールバック( GoodsRepositoryのgetCodesメソッドに渡す)
    const callback = ( result )=>{
      if( result != "success"){
        alert( result )
      }
    }
    
    //Vuexから検索文字列を取得
    searchQuery.value = store.state.searchQuery
    
    //商品情報取得
    getGoods( callback )
    
    //Unmounted時(別画面への遷移など)、Vuexに検索文字列設定
    onUnmounted(()=>{
      store.dispatch('setSearchQuery', searchQuery.value)
    })
    
    //template上で使用する変数を宣言
    return{
      goods:matchGoods,
      addGoods,
      searchQuery,
      getGoods,
      categoryDatas,
      sort
    }
  }
})

</script>

<style>
 省略
</style>

・GoodsRepository.js

商品取得、登録、編集用モジュール

※このモジュールは商品一覧、商品登録画面両方から使用される。

import { ref } from 'vue'
import { fetchGoods, fetchGoodsById, sendGoods } from '../api/serveraccess'

//商品情報の取得、登録、編集などの機能を提供する
export default function (){
  //商品一覧用変数の宣言、ref(リアクティブな値)として宣言
  let goodsList = ref([])

  //商品登録、編集用変数の宣言、refとして宣言
  let goods = ref({code:'',name:'',category:'001',price:''})

  //カテゴリのマスタを宣言、refとして宣言
  const categoryDatas = ref([])
  categoryDatas.value.push({id:'001', name:'本'})
  categoryDatas.value.push({id:'002', name:'ゲーム'})

 //商品取得メソッド宣言、非同期メソッドとして宣言
  //sereraccess.jsのメソッドをコール
  const getGoods = async( callback ) => {
    try{
      goodsList.value = await fetchGoods()
      callback("success")
    }catch(e){
      callback(e)
    }
  }

  //商品をIDで指定して取得するメソッド宣言、非同期メソッドとして宣言
  //sereraccess.jsのメソッドをコール
  const getGoodsById = async( id, callback ) =>{
    try{
      goods.value = await fetchGoodsById(id)
      callback("success")
    }catch(e){
      callback(e)
    }
  }
  
  //商品登録、更新メソッド宣言、非同期メソッドとして宣言
  //sereraccess.jsのメソッドをコール
  const registGoods = async( regGoods, callback  ) => {
    try{
      await sendGoods( regGoods )
      callback("success")
    }catch(e){
      callback(e)
    }
  }
 
  //当該モジュールが提供する変数、メソッドを宣言
  return{
    goodsList,
    goods,
    getGoods,
    getGoodsById,
    registGoods,
    categoryDatas
  }
}

・GoodsFilter.js

商品名検索用モジュール

import { ref, computed } from 'vue'

//商品リストを渡してもらう
export default function ( goodsList ){
  
  //検索文字列を宣言、refとして宣言
  //GoodsList.vueのinput項目と関連付けている
  const searchQuery = ref('')

  //searchQuery変更時に常に当該ロジックが起動、Vue.jsのcomputed機能を使用する
  //検索で絞られた値を格納する変数をreturnで宣言し外部で使用する事が出来る
  const matchGoods = computed(() => {
    return goodsList.value.filter(goods => {
      return goods.name.includes(searchQuery.value)
    })
  })

  //当該モジュールが提供する変数、メソッドを宣言
  return{
    searchQuery,
    matchGoods
  }
}

・GoodsSort.js

商品リストソート用モジュール

//商品リストを渡してもらう
export default function ( goodsList ){

  let orderColumn = ''
  let orderBy = ''

  //ソートメソッドを宣言
  //引数のカラムのソート処理を行う。
  //現在が昇順の場合は降順に、降順の場合は昇順にソート
  const sort = ( column ) =>{
  
    if( orderColumn != column ){
      orderColumn = column
      orderBy = "asc"
    }else{
      if( orderBy == "asc"){
        orderBy = "desc"
      }else{
        orderBy = "asc"
      }
    }
  
    goodsList.value.sort(
     (a, b)=>{
       let res1 = 1;
       let res2 = -1
       if( orderBy == "desc"){
         res1 = -1
         res2 = 1
       }
       if( a[column] > b[column] ){
         return res1
       }else{
         return res2
       }

     }
    )
  }

  //当該モジュールが提供する変数、メソッドを宣言
  return{
    sort
  }
}

商品登録・編集画面

・EditGoods.vue

商品登録、編集画面用コンポーネント

<template>
	<div>
		<div class="inputItemDiv"><div class="inputTitleDiv">コード</div><input readonly class="inputread" type="text" v-model="goods.code"></div>

		<div class="inputItemDiv"><div class="inputTitleDiv">商品名</div><input class="input" type="text" v-model="goods.name"></div>
		<div class="inputItemDiv" v-show="!resNameVal"><div class="inputTitleDiv"></div><span class="validateError">入力必須。50桁以内で入力して下さい。</span></div>
		
		<div class="inputItemDiv"><div class="inputTitleDiv">カテゴリ</div>
			<select class="select" name="category" v-model="goods.category">
				<option v-for="categoryData in categoryDatas" :value="categoryData.id" :key="categoryData.id">
					{{ categoryData.name }}
				</option>
			</select>
		</div>
		<div class="inputItemDiv"><div class="inputTitleDiv">価格</div><input class="input" type="text" v-model="goods.price"></div>
		
		<div class="inputItemDiv" v-show="!resPriceVal"><div class="inputTitleDiv"></div><span class="validateError">入力必須。半角数字7桁以内で入力して下さい。</span></div>
		
		
		<div class="inputItemDiv" style="text-align:right">
			<button  type="button" class="button" @click='cancel'>キャンセル</button>
			<button :disabled="resAllVal"  type="button" class="button" @click='regist'>登録</button>
		</div>
		

	</div>

</template>

<script>
import goodsValidator from '../composables/GoodsValidator'
import goodsRepository from '../composables/GoodsRepository'
import { useRoute, useRouter  } from 'vue-router'

export default ({
  name: 'GoodsList',
  
  setup(){
    //GoodsRepositoryモジュールからの変数、メソッドを宣言
    const { categoryDatas, goods, getGoodsById, registGoods } = goodsRepository()

    //ルータ使用変数宣言
    const route = useRoute()
    const router = useRouter()
    
    //パラメータでIDが渡された際は、商品情報を取得する
    if( route.params.id != null ){
      getGoodsById( route.params.id, getCallback  )
    }
    
    //データ取得時のコールバック( GoodsRepositoryのgetGoodsByIdメソッドに渡す)
    const getCallback = ( result ) =>{
      if( result != "success"){
        alert( result );
      }
    }

    //GoodsValidatorモジュールからの変数、メソッドを宣言
    //resName:名称入力チェック結果
    //resPricceVal:価格入力チェック結果
    //resAllVal:全ての項目の入力チェック結果
    const{ resNameVal, resPriceVal, resAllVal } = goodsValidator( goods )


    //登録ボタン押下時の処理
    const regist = () => {
      registGoods( goods.value, regCallback )
    }
    
    //登録ボタン押下時のコールバック( GoodsRepositoryのregistGoodsメソッドに渡す)
    const regCallback = ( result ) =>{
      if( result == "success"){
        alert("登録完了");
        router.push("/")
      }else{
        alert( result );
      }
    }
    
    //キャンセル処理 商品一覧画面へ戻る
    const cancel = () => {
      router.push("/")
    }
    
    //template上で使用する変数を宣言
    return{
      categoryDatas,
      goods,
      regist,
      cancel,
      resNameVal,
      resPriceVal,
      resAllVal,     
    }
  }

})
</script>
<style>
  省略
</style>
import { computed } from 'vue'
import { validLength, validInteger } from '../api/checker'

export default function ( goods ){

  //商品名用チェック
  const nameValidator = () =>{
	return validLength(1, 10, goods.value.name)
  }

  //価格用チェック
  const priceValidator = () =>{    
    return validInteger(1,7, goods.value.price)
  }	
  
  //全てのチェック
  const allValidator = () =>{
    var res = ( computed( nameValidator ).value && computed( priceValidator ).value ) 
    console.log( res )
    if( res ){
      return false
    }else{
      return true
    }
  }
  
  //computedに設定
  //商品名、価格の入力が変化した際に処理が起動
  //全チェックは登録ボタン押下可否に使用する
  const resNameVal = computed( nameValidator )
  const resPriceVal = computed( priceValidator )
  const resAllVal = computed( allValidator )

  //当該モジュールが提供する変数、メソッドを宣言
  return{
	resNameVal,
	resPriceVal,
	resAllVal
  }
}

その他

・ckecker.js

値チェック用モジュール

//桁数チェック
let validLength = (min, max, value) => {
  if( value.length >= min && value.length <= max ){
    return true
  }
  return false
}

//数値桁数チェック
let validInteger = (minLength, maxLength, value) => {
    var pattern = '^[0-9]{' + minLength + ',' + maxLength + '}$'; 
    console.log( pattern )
    return value.match( pattern )
}

//必須チェック
let validRequire = ( value) => {
    if( value == null || value.length == 0 ) return false
    return true
}

export{ validLength, validInteger, validRequire }

・serveraccess.js

サーバへのアクセスモジュール

import * as axios from 'axios'

//商品全権取得
let fetchGoods = ( ) => {
  
  return new Promise((resolve, reject) => {
      axios
        .get('http://localhost:8080/goods/list')
        .then(response => {
           resolve(response.data)
         }).catch(error => {
            reject(error)
         })
    }).catch(() => {
      throw 'サーバアクセスエラー'
    })
}

//商品をID指定で取得
let fetchGoodsById = (id) => {
    

  return new Promise((resolve, reject) => {
      axios
        .get('http://localhost:8080/goods/byId?id=' + id)
        .then(response => {
           resolve(response.data)
         }).catch(error => {
            reject(error)
         })
    }).catch(() => {
      throw 'サーバアクセスエラー'
    })


}

//商品登録、更新
let sendGoods = (goods) => {
    
  return new Promise((resolve, reject) => {
      axios
        .post('http://localhost:8080/goods/regist', goods )
        .then(response => {
           resolve(response.data)
         }).catch(error => {
            reject(error)
         })
    }).catch(() => {
      throw 'サーバアクセスエラー'
    })
    
}

export{ fetchGoods, fetchGoodsById, sendGoods }

以上、画面側の説明でした。
Composition Apiになってコードをモジュール事に小分けする事が可能になりました。
これにより、モジュール事に役割が明確になり実装が用意かつ管理しやすくなったのが分かります。
コードはにアップしてあります。
サーバ側は次回に簡単に説明したいと思います。

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

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

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

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