Chuyển đến nội dung chính

レッスン 8: フォーム、検証、スタイル設定

フォーム処理、v-model 修飾子。 VeeValidate、Zod スキーマ検証。 Tailwind CSS、UnoCSS。コンポーネント ライブラリ (PrimeVue、Vuetify、Naive UI)。 CSS スコープの CSS モジュール。

💻 プログラミング — レッスン 8 レッスン 8: フォーム、検証、スタイル設定

Vue.js と Nuxt: 基本から高度まで

パート 2: Vue エコシステム

xdev.asia

1. フォームの処理

<script setup lang="ts">
import { reactive } from 'vue'

const form = reactive({
  name: '',
  email: '',
  role: 'user',
  notifications: true,
  tags: [] as string[],
})

function handleSubmit() {
  console.log(form)
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model.trim="form.name" placeholder="Tên" />
    <input v-model.lazy="form.email" type="email" placeholder="Email" />

    <select v-model="form.role">
      <option value="user">User</option>
      <option value="admin">Admin</option>
    </select>

    <label>
      <input type="checkbox" v-model="form.notifications" />
      Nhận thông báo
    </label>

    <div>
      <label v-for="tag in ['vue', 'nuxt', 'ts']" :key="tag">
        <input type="checkbox" v-model="form.tags" :value="tag" />
        {{ tag }}
      </label>
    </div>

    <button type="submit">Submit</button>
  </form>
</template>

2. VeeValidate + Zod

npm install vee-validate @vee-validate/zod zod
<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { z } from 'zod'

const schema = toTypedSchema(
  z.object({
    name: z.string().min(2, 'Tên ít nhất 2 ký tự'),
    email: z.string().email('Email không hợp lệ'),
    password: z
      .string()
      .min(8, 'Mật khẩu ít nhất 8 ký tự')
      .regex(/[A-Z]/, 'Cần ít nhất 1 chữ hoa'),
  })
)

const { defineField, handleSubmit, errors, isSubmitting } = useForm({
  validationSchema: schema,
})

const [name, nameAttrs] = defineField('name')
const [email, emailAttrs] = defineField('email')
const [password, passwordAttrs] = defineField('password')

const onSubmit = handleSubmit(async (values) => {
  await fetch('/api/register', {
    method: 'POST',
    body: JSON.stringify(values),
  })
})
</script>

<template>
  <form @submit="onSubmit">
    <div>
      <input v-model="name" v-bind="nameAttrs" placeholder="Tên" />
      <span v-if="errors.name" class="error">{{ errors.name }}</span>
    </div>

    <div>
      <input v-model="email" v-bind="emailAttrs" type="email" placeholder="Email" />
      <span v-if="errors.email" class="error">{{ errors.email }}</span>
    </div>

    <div>
      <input v-model="password" v-bind="passwordAttrs" type="password" placeholder="Mật khẩu" />
      <span v-if="errors.password" class="error">{{ errors.password }}</span>
    </div>

    <button type="submit" :disabled="isSubmitting">Đăng ký</button>
  </form>
</template>

3. 追い風 CSS

<template>
  <div class="max-w-md mx-auto p-6 bg-white dark:bg-gray-800 rounded-lg shadow-lg">
    <h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
      {{ title }}
    </h2>
    <p class="text-gray-600 dark:text-gray-300 leading-relaxed">
      {{ description }}
    </p>
    <button class="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md 
                   hover:bg-blue-700 transition-colors duration-200
                   focus:outline-none focus:ring-2 focus:ring-blue-500">
      Xem thêm
    </button>
  </div>
</template>

4. CSS スコープとモジュール

<!-- Scoped CSS -->
<style scoped>
.card { padding: 1rem; }
.card :deep(.child-class) { color: red; } /* Affect child components */
.card :slotted(p) { margin: 0; } /* Affect slot content */
:global(.app-title) { font-size: 2rem; } /* Global style */
</style>

<!-- CSS Modules -->
<style module>
.red { color: red; }
.bold { font-weight: bold; }
</style>

<template>
  <p :class="$style.red">Red text</p>
  <p :class="[$style.red, $style.bold]">Red bold</p>
</template>

5. コンポーネントライブラリ

図書館スタイルコンポーネントバンドル
プライムビュースタイルなし/テーマあり90+ツリーシェイク可能
ヴエティファイマテリアルデザイン80+大
素朴なUIカスタムテーマ90+ツリーシェイク可能
Radix Vueヘッドレス40歳以上最小限
shadcn-vue追い風40歳以上コピー&ペースト

次の記事: Nuxt 3 とファイルベースのルーティング — Nuxt 3 フレームワークを始めましょう。