追記
2020/05/31 別記事として発展編書きました
問題意識
ドメイン駆動設計に従うVue/Nuxtアプリケーションを作っていて、
名前フォームに文字列を打ち込んで8文字より多く書かれたとき、
そのフォームの一つ上に赤い文字で「名前は8文字以内です」というエラーをリアルタイムで表示してほしい、
……という要件があるとしましょう。
このときVue側のコードで、インプットフォームから受け取った変数に8文字という制限をすることも可能なのですが、
「それって 利口なUI という奴なのでは?」と考えていました。
一般に利口なUIはアンチパターンとして知られていて、(議論はありますが)たしかに、ドメイン知識がUI層に流出しています。
どのみちエンジニア的にも同じ内容を2度書くような気がして、利口なUIは利口じゃないコードになりかねません。
そういうわけで自分なりにその解決をしてみたいと思います。
完成品
devinoue/realtime-validation-ddd
環境
Nuxt2.12.2
Composition API
TypeScript
ドメインを書く
今回はNameクラスをTypeScriptで書きますが、必要そうな所だけです。
ひとまずdomainというディレクトリに以下のような値オブジェクトとしてName.tsを入れておきます。
export default class Name {
constructor(private _name: string) {
Name.validation(this._name)
}
static validation(name: string): never | void {
if (name === '') {
throw new Error('名前を入力してください')
}
if (typeof name !== 'string') {
throw new TypeError('名前は文字列にしてください')
}
if (name.length > 8) {
throw new Error('名前は8文字以内にしてください')
}
}
}
ついでに、Eメールアドレス用値オブジェクトも作っておきます。
コード的にはほぼ同じになってしまうので、こちらから御覧ください。
ハンドラを書く
Composition APIで作るので、ハンドラも切り分けておきます。
別にそういうvue界の慣習があるわけではありませんし、切り分けなくてもいいのですが、
この方が見晴らしがいいという理由で分けています。
ちなみにDDDでいうプレゼンテーション層に属するものとして扱っています。
ここからドメイン知識にアクセスしていますが、生成メソッドではないため許容されるという認識です。
import { ref, watch } from '@vue/composition-api'
import Name from '~/domain/Name'
import EmailAddress from '~/domain/EmailAddress'
interface IForm {
name: string
email: string
}
export default function() {
const defaultInput: IForm = { name: '', email: '' }
const forms = reactive({ ...defaultInput })
const errors = reactive({ ...defaultInput })
watch(
() => forms.name,
() => {
errors.name = ''
try {
Name.validation(forms.name)
} catch (e) {
errors.name = e.message
}
},
{ lazy: true }
)
watch(
() => forms.email,
() => {
errors.email = ''
try {
EmailAddress.validation(forms.email)
} catch (e) {
errors.email = e.message
}
},
{ lazy: true }
)
return { forms, errors }
}
ここ、無駄が多い気がしますが、watch関数を使っている手前まとめにくい、、、、
vueファイルを完成させる
さて、残りはpages以下のindex.vueで、さきほど作ったファイルを読み込むだけです。
<template>
<div class="container">
<span v-show="errors.name" class="error">{{ errors.name }}</span>
<span>名前 : <input v-model="name" type="text"/></span>
<br />
<span v-show="errors.email" class="error">{{ errors.email }}</span>
<span>メールアドレス : <input v-model="email" type="text"/></span>
<br />
</div>
</template>
<script lang="ts">
import useInputHandler from '~/handler/InputHandler'
export default {
setup() {
return { ...useInputHandler() }
}
}
</script>
// スタイル省略
御覧ください!! ほとんど空っぽ! なんとスッキリしているのでしょう!!!😭
今までのVueファイルがウソのようにキレイにまとめられました!
動作イメージ
何か書き込むたびにwatchメソッドが変数を監視して、エラーを報告してくれます。
これで「利口なUI」とならずに済みます😄
終わりに
Vue3素晴らしい……!
GitHub:
devinoue/realtime-validation-ddd