プロジェクトが進んで行くと同じような処理が複数のファイルに出てきて、共通化したくなってきます。(スコープ分けたかったので、今までは親子componentの分割に留めました)
共通化する事で、可読性が上がり保守・改修コストが抑えられたり、潜在的な不具合(バグ)を減らす事ができます。
共通化の方法はいくつかあり、良し悪しがありますので、ケース毎に使い分けるのが良さそう。
今回はmixinとinjectを使い、命名規則も決めて、可読性を意識してリファクタリングしました。
mixinで共通化
mixinは賛否両論あると思いますが、script内をmixin先に委ねられるので便利です。
mixin元では、唐突にimportやdataの変数、methodが出てくるので、敬遠する人もいますが、個人的にはRailsの継承をよく使っていたので、違和感は少なかったです。
ただ、名前の通り、継承ではなくミックスなので、継承元が優先される訳ではなく、両方実行されます。この時、mixin先が先に実行されるのと、await使っている場合だけかもですが、終了を待たずにmixin元やmounted(createdに対して)が実行されるので、mixin先で共通化できない場合もありそうです。
mixin先
目的に応じて分けるのも良いかもですが、複数あると複雑になるので、先ずは1つにしました。
ページにより使っていないcomponentやmethodもありますが、実害がないのでそのままに。
plugins/application.js を作成
import Loading from '~/components/Loading.vue' import Message from '~/components/Message.vue' export default { components: { Loading, Message }, data () { return { loading: true, processing: true, alert: null, notice: null } }, methods: { appSetQueryMessage () { this.alert = this.$route.query.alert this.notice = this.$route.query.notice this.$router.push({ path: this.$route.path }) // Tips: URLパラメータを消す為 }, appRedirectSuccess (alert, notice) { this.$toasted.error(alert) this.$toasted.info(notice) this.$router.push({ path: '/' }) } } }
mixin元から移しています。
処理はメソッドにして、命名をapp〜とする事で、mixin先のapplication.jsにありそうだなと解るようにと、被らないようにしています。
mixin元
pages/users/sign_in.vue を変更
- import Loading from '~/components/Loading.vue' - import Message from '~/components/Message.vue' + import Application from '~/plugins/application.js'
components: { ValidationObserver, - ValidationProvider, + ValidationProvider - Loading, - Message }, + mixins: [Application],
mixin先に移したのを削除して、application.jsをmixin
data () { return { - loading: true, - processing: true, - alert: null, - notice: null, email: '', password: '' }
ここもmixin先に移したのを削除
created () { <省略> if (this.$auth.loggedIn) { - this.$toasted.error(this.$route.query.alert) - this.$toasted.info(this.$route.query.notice) - return this.$router.push({ path: '/' }) + return this.appRedirectSuccess(this.$route.query.alert, this.$route.query.notice) }
- this.alert = this.$route.query.alert - this.notice = this.$route.query.notice - this.$router.push({ path: this.$route.path }) // Tips: URLパラメータを消す為 + this.appSetQueryMessage()
mixin先でメソッド化して移しています。
this.processing = false this.loading = false },
本当は、mixin先で共通化したかったのですが、mixin先の方が先に実行されるのと、mixin先のmountedが先に実行されてしまうので、冗長になりますが、mixin元で対応しました。
可読性向上
clickイベントは、on〜とする事にしました。他でも良く見ます。
- <v-btn color="primary" :disabled="invalid || processing" @click="signIn"> + <v-btn color="primary" :disabled="invalid || processing" @click="onSignIn()">
methods: { - async signIn () { + async onSignIn () {
injectで共通化
computedもmixinで出来ますが、mixinしていないlayouts/default.vueでも使いたかったので、injectにしました。
普通にjsをimportでも良いのですが、ページ毎の手続きが不要で、$〜で呼び出すので、pluginsにありそうだなと解りそう。
nuxt.config.js に追加
plugins: [ { src: '~/plugins/axios.js' }, + { src: '~/plugins/utils.js' } ],
plugins/utils.js を作成
const dateFormat = (date, locales) => { if (date == null) { return } const dtf = new Intl.DateTimeFormat(locales, { year: 'numeric', month: '2-digit', day: '2-digit' }) return dtf.format(new Date(date)) } export default (_context, inject) => { inject('dateFormat', dateFormat) }
layouts/default.vue を変更
- このアカウントは{{ dateFormat($auth.user.destroy_schedule_at) }}以降に削除されます。 + このアカウントは{{ $dateFormat($auth.user.destroy_schedule_at, 'ja') }}以降に削除されます。
computed: { displayItems () { return this.items.filter(item => (item.loggedIn === null) || (item.loggedIn === this.$auth.loggedIn)) - }, - dateFormat () { - return function (date) { - const dtf = new Intl.DateTimeFormat('ja', { year: 'numeric', month: '2-digit', day: '2-digit' }) - return dtf.format(new Date(date)) - } } }