LoginSignup
5
4

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-10-21

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 で書き直したソースの抜粋です。

5
4
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
5
4