課題
追加と編集フォームは同じデータを操作することが多い。そのため、各フォームを異なるコンポーネントとして作成するとほぼ同じ処理をするコンポーネントが2つできることになり、入力チェックやレイアウトの調整に2重のコストがかかる。そのため、追加と編集フォームを共通化したい。
解決方法
あまりスマートな方法を見つけられなかったので、以下、愚直に実装した例。
- 子コンポーネントに、入力チェックや画面のレイアウトなどの共通処理を実装する
- 子コンポーネントで入力チェックが終わったデータを、親コンポーネントにイベントとして通知する
- 親コンポーネントで子からイベントを受け取り、追加/編集固有の処理ロジックを実装する
検証環境
package.json
...
"dependencies": {
"vue": "^2.5.2",
"vue-router": "^3.0.1"
},
...
子コンポーネント
- 初期表示するためのデータを親から
props
で受け取る - 子コンポーネントに、入力チェックや画面のレイアウトなどの共通処理を実装する
- 入力チェック等が終わったデータを、親コンポーネントにイベントとして通知する
<template>
<div class="panel panel-default">
<div class="panel-heading">
{{title}}
</div>
<div class="panel-body">
<form @submit.prevent="submit">
<div class="form-group">
<label for="name">名前</label>
<input type="text" class="form-control" id="name" v-model="form.name" required maxlength="5"/>
</div>
<div class="form-group">
<label for="role">権限</label>
<select v-model="form.selectedRoleId" id="role" class="form-control" required>
<option v-for="role in roles" :value="role.id" :key="role.id">
{{role.name}}
</option>
</select>
</div>
<button type="submit">保存する</button>
</form>
</div>
</div>
</template>
<script>
export default {
name: 'Form',
props: {
initialName: String,
initialSelectedRoleId: Number,
title: String
},
data () {
return {
roles: [],
form: {
name: this.initialName,
selectedRoleId: this.initialSelectedRoleId
}
}
},
mounted: function () {
// serverからデータを取得する
this.roles = [
{
id: 1,
name: 'admin'
},
{
id: 2,
name: 'leader'
},
{
id: 3,
name: 'member'
}
]
},
methods: {
submit: function () {
// validationやらの入力チェックをして、okだったらsubmitイベントを出す
console.log('validation ... ')
this.$emit('submit', this.form)
}
}
}
</script>
親コンポーネント
- 子の
submit
イベントを拾ってサーバへの追加といった固有処理を記述する
<template>
<base-form
title="ユーザの追加"
@submit="add"
></base-form>
</template>
<script>
import BaseForm from './BaseForm'
export default {
name: 'AddForm',
methods: {
add: function (form) {
// サーバに保存する、など
console.log('send server ...', JSON.stringify(form))
}
},
components: { BaseForm }
}
</script>
- 編集フォームも同じ
<template>
<base-form
title="ユーザの編集"
:initialName="name"
:initialSelectedRoleId="selectedRoleId"
@submit="edit"
></base-form>
</template>
<script>
import BaseForm from './BaseForm'
export default {
name: 'EditForm',
data () {
return {
name: '',
selectedRoleId: ''
}
},
created: function () {
// サーバから最新データを取得する
this.name = '管理し太郎'
this.selectedRoleId = 1
},
methods: {
edit: function (form) {
// サーバに保存する、など
console.log('send server ...', JSON.stringify(form))
}
},
components: { BaseForm }
}
</script>
実行結果
- 追加と編集フォームを共通化できた
補足
今回は親子間でデータの同期をしていないが、v-model
でデータを同期したければ以下が参考になる。
参考 github issues
親コンポーネント側でコンテンツを差し替えたい要素があれば<slot>
を利用する。
今回は$emit
のやりとりで親コンポーネントにデータを渡しているが、子側に追加/編集フォーム固有処理を記述したイベントハンドラを渡す方法もある。処理は異なるけど見た目とロジックの一部は再利用したい、という場合のベストプラクティスがよくわかってないので、これ良いよという方法があれば教えてほしいです!