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