1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

意気揚々とAtomic Designを導入したら、v-modelとmethodsに躓いたお話

Posted at

Atomic Designとは

パーツ・コンポーネント単位で定義していく、UIデザイン手法です。

最小の単位「原子」(Lv1)からデザインし、「分子」(Lv2)、「生体」(Lv3)、「テンプレート」(Lv4)、「ページ」(Lv5)の順にデザイン作業を進めていきます。

今回はフォームを作成しました。

Atoms Template
FormInput.vue Form.vue
FormButton.vue

Atomsにフォームの入力欄FormInputフォームの送信ボタンFormButtonの作成をします。
Templateに、Atomsを使って作成したフォームをFormとして作成します。

以下の記事にもあるように、完璧な線引きは難しいので私は適当にAtomsとTemplateを使います。

image.png

FormInput.vueを作成する

FormInput.vueでは、v-modelでの値の渡し方に躓きました。

image.png

ありがちなミスですが、親から受け取ったvaluev-modelにしました。
ただこれではうまくいきません。

Atoms/FormInput.vue
<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/Form.vue
<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>

次に子です。

Atoms/FormInput.vue
<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>
Atoms/Types/FormInput.type.ts
export type FormInputType = 'text' | 'email' | 'password';

これで無事に動きました!

image.png

FormButton.vueを作成する

FormButton.vueでは、methodsの使い方に躓きました。
方法としては以下の2つです。
Vuexを使う
$emitpropsを使う

VuexとTypeScriptの相性が悪いので、①はなし。
なので②の方法を考えました。

$emitを使わずに作る

以下の記事を参考にしました。
TypeScriptを使うのでpropsの成約をつけられるというメリットでこちらを採用しました。

ということで実際にコードを書いてみましょう。
まずは親からです。

Template/Form.vue
<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>

次に子です。

Atoms/FormButton.vue
<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>
Atoms/Types/FormButton.type.ts
export type ClickType = {
  (): void;
};

export type Props = {
  click: Function;
};

これで完成です。

image.png

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?