Help us understand the problem. What is going on with this article?

Vue.js + TypeScript で親子コンポーネントのデータの受け渡し

SPA ログイン画面の例です。
記法は vue-property-decorator、CSS は bulma を使っています。
似たようなソースは共通のコンポーネントにしたい! 
コード中にコメントしてみます。

親コンポーネント (login.vue)

pages/login.vue
<template>
  <div>
    <!-- Email 入力 (自作の子コンポーネント) -->
    <!-- v-model="users.email"で親の users.email を双方向に書き換えられるようにしています。 -->
    <!-- その他の name= や type= (任意の名前) はカスタム属性です。親 -> 子へデータを渡します。-->
    <!-- 例えば name="email" だと、子に @Prop で name という箱を用意してやると "email" という文字列を受け取れます。 -->
    <base-input
      v-model="users.email"
      name="email"
    />
    <!-- パスワード入力 (自作の子コンポーネント) -->
    <base-input
      v-model="users.password"
      name="password"
      type="password"
    />
    <!-- ログインボタン (自作の子コンポーネント) -->
    <!-- 子の @Emit で clicked (任意の名前) を発火し、さらに private login() を発火させます。発火リレー。 -->
    <base-button
      @clicked="login"
      label="login"
      view="primary"
      validation="true"
    />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import LoginInterface from '@/interfaces/LoginInterface';

@Component
export default class Login extends Vue {
  private users: LoginInterface = { email: '', password: '' };

  // base-button の @clicked から発火されます。
  private login(): void {
    this.$stores.auth.create(this.users);
  }
}
</script>

ちなみにインターフェイスはこんな感じです。

LoginInterface.ts
export default interface LoginInterface {
  email: string;
  password: string;
}

子コンポーネント (BaseInput.vue)

components/atoms/BaseInput.vue
<template>
  <div class="field">
    <div class="control">
      <!-- ポイントは v-model:value="inputValue" -->
      <!-- private get inputValue() を通して親の値をもらい、private set inputValue(value: string) で親に値を送ります。 -->
      <!-- :placeholder はプレースホルダー名を vue-i18n で言語ファイルから取り出せるようにしています。 -->
      <!-- v-validate は plugin で定数ファイルからバリデーションルールを取り出せるようにしています。 -->
      <input
        :class="inputClass"
        :placeholder="$t(`label.${name}`)"
        v-validate="this.$config.validators[name]"
        :data-vv-as="$t(`label.${name}`)"
        :name="name"
        v-model:value="inputValue"
        :type="type"
        autofocus=""
      >
      {{ errors.first(name) }}
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class BaseInput extends Vue {
  // @Prop は、先ほど親でカスタム属性に指定した変数名にします。
  @Prop({ default: '' })
  private name!: string;
  @Prop({ default: '' })
  private value!: string;
  @Prop({ default: 'text' })
  private type!: string;

  // ゲッター (computed)
  private get inputValue(): string {
    return this.value;
  }

  // セッター
  private set inputValue(value: string) {
    // デコレーターの @Emit を使用する方法もあるのですが冗長なのでこちらで。
    this.$emit('input', value);
  }

  // class は親からカスタム属性で設定できるようにゲッターで管理してみました。
  // https://jp.vuejs.org/v2/guide/class-and-style.html#HTML-%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E3%83%90%E3%82%A4%E3%83%B3%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0
  private get inputClass(): object {
    return {
      'input': true,
      'is-large': true,
    };
  }
}
</script>

子コンポーネント (BaseButton.vue)

components/atoms/BaseButton.vue
<template>
  <div class="field">
    <!-- ポイントは @click="clicked" -->
    <!-- @Emit() private clicked() を通じて親の clicked() をコールします。 -->
    <button
      :class="buttonClass"
      @click="clicked"
      :disabled="validation && disabled"
      :name="label"
    >
      {{ $t(`label.${label}`) }}
    </button>
  </div>
</template>

<script lang="ts">
import { Component, Emit, Prop, Vue } from 'vue-property-decorator';

@Component
export default class BaseButton extends Vue {
  @Prop({ default: '' })
  private label!: string;
  @Prop({ default: '' })
  private view!: string;
  @Prop({ default: false })
  private validation!: boolean;

  // @Emit('clicked') と同義です。親と同じ function 名の場合は @Emit() で省略可能です。
  @Emit()
  private clicked(): void {}

  private get disabled(): boolean {
    // vee-validate
    return this.$validator.errors.items.length > 0;
  }

  private get buttonClass(): object {
    return {
      'button': true,
      'is-block': true,
      'is-info': true,
      'is-large': this.view === 'primary',
      'is-fullwidth': true,
    };
  }
}
</script>

備考

以前投稿した「Vue.js で簡単なログイン画面 (トークン認証) を作ってみた」 を TypeScript で書き直したソースの抜粋です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした