Atomic Designとは
パーツ・コンポーネント単位で定義していく、UIデザイン手法です。
最小の単位「原子」(Lv1)からデザインし、「分子」(Lv2)、「生体」(Lv3)、「テンプレート」(Lv4)、「ページ」(Lv5)の順にデザイン作業を進めていきます。
今回はフォームを作成しました。
Atoms | Template |
---|---|
FormInput.vue | Form.vue |
FormButton.vue |
Atomsにフォームの入力欄のFormInput
、フォームの送信ボタンのFormButton
の作成をします。
Templateに、Atomsを使って作成したフォームをForm
として作成します。
以下の記事にもあるように、完璧な線引きは難しいので私は適当にAtomsとTemplateを使います。
FormInput.vue
を作成する
FormInput.vue
では、v-modelでの値の渡し方に躓きました。
ありがちなミスですが、親から受け取ったvalue
をv-model
にしました。
ただこれではうまくいきません。
<template>
<input v-model="value" />
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, PropType } from '@nuxtjs/composition-api';
export default defineComponent({
props: {
value: {
type: String,
required: true,
},
},
});
何故なら、value
は親から渡された値なので、読み取り専門になるからです。
子で値を上書きしても、その値が親まで伝わりません。
v-model
を使った値の渡し方
答えはこの記事にありました。
要約すると、v-model
は:value
と@input
の糖衣構文だから分解しなさいという内容です。
ということで実際にコードを書いてみましょう。
まずは親からです。
<template>
<div class="form-container">
<FormInput :type="'text'" :placeholder="'姓'" :value="firstName" @input="firstName = $event" />
<FormInput :type="'text'" :placeholder="'名'" :value="lastName" @input="lastName = $event" />
<FormInput
:type="'email'"
:placeholder="'メールアドレス'"
:value="email"
@input="email = $event"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from '@nuxtjs/composition-api';
// components
import FormInput from '@/components/Atoms/FormInput.vue';
export default defineComponent({
components: {
FormInput,
},
setup() {
const firstName = ref<string>('');
const lastName = ref<string>('');
const email = ref<string>('');
return {
firstName,
lastName,
email,
};
},
});
</script>
次に子です。
<template>
<div>
<input
class="form-input"
:type="type"
:placeholder="placeholder"
:value="value"
@input="$emit('input', $event.target.value)"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, PropType } from '@nuxtjs/composition-api';
// types
import { FormInputType } from './types/FormInput.type';
export default defineComponent({
props: {
type: {
type: String as PropType<FormInputType>,
default: 'text',
validator: function(value: string) {
return ['text', 'email', 'password'].indexOf(value) !== -1;
},
},
placeholder: {
type: String,
required: true,
},
value: {
type: String,
required: true,
},
},
});
</script>
export type FormInputType = 'text' | 'email' | 'password';
これで無事に動きました!
FormButton.vue
を作成する
FormButton.vue
では、methodsの使い方に躓きました。
方法としては以下の2つです。
①Vuex
を使う
②$emit
やprops
を使う
VuexとTypeScriptの相性が悪いので、①はなし。
なので②の方法を考えました。
$emit
を使わずに作る
以下の記事を参考にしました。
TypeScriptを使うのでprops
の成約をつけられるというメリットでこちらを採用しました。
ということで実際にコードを書いてみましょう。
まずは親からです。
<template>
<div class="form-container">
<FormButton :click="submit" @submit="submit" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from '@nuxtjs/composition-api';
// components
import FormButton from '@/components/Atoms/FormButton.vue';
export default defineComponent({
components: {
FormButton,
},
setup() {
const submit = async (): Promise<void> => {
try {
await window.liff.sendMessages([
{
type: 'text',
text: firstName.value,
},
{
type: 'text',
text: lastName.value,
},
{
type: 'text',
text: email.value,
},
]);
await window.liff.closeWindow();
} catch (err) {
window.alert(err);
await window.liff.closeWindow();
}
};
return {
submit,
};
},
});
</script>
次に子です。
<template>
<div class="form-button">
<button class="button" @click="childClick">送信</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, PropType } from '@nuxtjs/composition-api';
// type
import { ClickType, Props } from './types/FormButton.type';
export default defineComponent({
props: {
click: {
type: Function as ClickType,
required: true,
},
},
setup(props: Props) {
// methods
const childClick = () => {
props.click();
};
return {
childClick,
};
},
});
</script>
export type ClickType = {
(): void;
};
export type Props = {
click: Function;
};
これで完成です。