Nuxt.js のサービス上でポイントを消費する際に確認するモーダルを作る。
モーダルには vue-thin-modal を使っている。
composable/point.tsimport { ref, computed } from '@nuxtjs/composition-api';
import { Accessor } from '@/store';
const needPoint = ref(0);
let confirmResolve: (ans: boolean) => void;
export const usePoint = ($accessor: Accessor, $modal: Vue['$modal']) => {
const modalName = 'point-confirmation-modal';
const currentPoint = computed(() => $accessor.user.point);
const hasEnoughPoint = computed(() => currentPoint.value - needPoint.value > 0);
const afterPoint = computed(() => currentPoint.value - needPoint.value);
const confirm = (ans: boolean) => {
confirmResolve(ans);
};
const openModal = async (point: number): Promise<boolean> => {
needPoint.value = point;
const promise = new Promise<boolean>((resolve): void => {
confirmResolve = resolve;
});
$modal.push(modalName);
const ans = await promise;
$modal.pop();
return ans;
};
return {
modalName,
needPoint,
currentPoint,
hasEnoughPoint,
afterPoint,
confirm,
openModal,
};
};
openModal
関数は needPoint
にこれから消費するポイントを保存した上で、新しい Promise オブジェクトを作る。
この Promise オブジェクトは resolve を confirmResolve
に保存することで、confirm
関数から resolve させることができる。
そして、モーダルを開き、confirm
関数によって Promise が resolve されるのを待ってからモーダルを閉じる。
最後に resolve の結果を返す。
これにより、コンポーネントからはモーダルを開けたり締めたりする制御を行う必要がなくなり、openModal
を呼び出す際に消費ポイントだけ渡して、結果を待てば良い。
実際の利用例は以下のようになる。
SomeActionComponent.vue<template>
<div>
<p>ポイントをつかってほげほげ</p>
<button @click="onClickHandler">はい</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api';
import { usePoint } from '@/composable/point';
export default defineComponent({
setup(_, { root }) {
const { openModal } = usePoint(root.$accessor, root.$modal)
const onClickHandler = async () => {
const ok = await openModal(50)
if (!ok) {
return
}
consumePoint()
}
return {
onClickHandler,
}
}
})
ConfirmPointConsumption.vue<template>
<modal :name="modalName" :disable-backdrop="true">
<div>
This will take {{ needPoint }} points. You have {{ currentPoint }} points.
<p v-if="hasEnoughPoint">
It will be {{ afterPoint }} points.
<button @click="ok">ok</button>
<button @click="cancel">cancel</button>
</p>
<p v-else>
You don't have enough points.
<button @click="cancel">ok</button>
</p>
</div>
</modal>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api';
import { usePoint } from '@/composable/point';
export default defineComponent({
setup(_, { root }) {
const { modalName, currentPoint, afterPoint, needPoint, hasEnoughPoint, confirm } = usePoint(
root.$accessor,
root.$modal,
);
const ok = () => {
confirm(true);
};
const cancel = () => {
confirm(false);
};
return {
modalName,
currentPoint,
afterPoint,
needPoint,
hasEnoughPoint,
ok,
cancel,
};
},
});
</script>
これを見ると、SomeActionComponent.vue
からはモーダル制御のロジックが、ConfirmPointConsumption.vue
からはモーダル制御及び残ポイント計算などのロジックが分離されていることが分かる。
これによりコンポーネントは「ユーザーのアクションによりロジックを呼び出す」、「計算結果を表示する」といったユーザーのインターフェースとして責務に専念できる。