jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Vue.js 3 · Phần 2 — Cú pháp Template & Directive

Bộ công cụ khai báo UI của Vue: interpolation, v-bind cho attribute, v-if vs v-show, v-for và vì sao cần key, v-on với event modifier, class/style binding động, và v-model ràng buộc hai chiều.

Template của Vue trông như HTML thường, nhưng được tăng sức mạnh bằng directive — các attribute đặc biệt bắt đầu bằng v- nối state với DOM. Phần này đi qua toàn bộ cú pháp template bạn dùng hằng ngày. Nắm chắc đây là biết “ngôn ngữ” để mô tả UI trong Vue.


1. Interpolation & biểu thức

{{ }} chèn giá trị vào text. Bên trong là biểu thức JavaScript, không phải câu lệnh:

<p>{{ user.name }}</p>
<p>{{ count + 1 }}</p>
<p>{{ ok ? 'Yes' : 'No' }}</p>
<p>{{ message.toUpperCase() }}</p>

Được: biểu thức đơn. Không được: if, vòng lặp, hay khai báo ({{ const x = 1 }} ✗). Nếu logic phức tạp, đẩy sang computed (Phần 3) cho gọn và cache.


2. v-bind — ràng buộc attribute

{{ }} chỉ cho text content. Để ràng buộc attribute, dùng v-bind, viết tắt là :.

<img v-bind:src="imageUrl" :alt="caption" />
<a :href="`/posts/${post.id}`">{{ post.title }}</a>
<button :disabled="isLoading">Gửi</button>

Khi giá trị là boolean (:disabled), Vue tự thêm/bỏ attribute. Bạn cũng bind cả một object attribute:

<input v-bind="{ type: 'text', placeholder: 'Tên', maxlength: 20 }" />

3. Class & style động

Ràng buộc class là việc làm nhiều nhất. Vue hỗ trợ cú pháp object và array:

<!-- object: bật class theo điều kiện -->
<div :class="{ active: isActive, 'text-danger': hasError }"></div>

<!-- array: danh sách class -->
<div :class="[baseClass, isActive ? 'active' : '']"></div>

<!-- kết hợp -->
<div :class="['card', { 'card--selected': selected }]"></div>

Style inline tương tự (dùng camelCase hoặc chuỗi kebab trong ngoặc):

<div :style="{ color: textColor, fontSize: size + 'px' }"></div>

class tĩnh và :class động gộp với nhau, nên bạn để class cố định ở class và phần động ở :class.


4. Render điều kiện: v-if vs v-show

<p v-if="status === 'loading'">Đang tải…</p>
<p v-else-if="status === 'error'">Lỗi rồi</p>
<p v-else>Xong</p>

<div v-show="isOpen">Panel</div>

Khác biệt then chốt:

v-ifv-show
Cách ẩngỡ hẳn khỏi DOMgiữ DOM, set display: none
Chi phídựng/hủy lại mỗi lần toggletoggle CSS, rẻ
Dùng khiít khi đổi, hoặc nội dung nặngbật/tắt thường xuyên

Quy tắc: toggle liên tục (tab, dropdown) → v-show; điều kiện hiếm đổi hoặc khối nặng → v-if.

v-if cần một thẻ. Để điều kiện một nhóm phần tử mà không thêm DOM thừa, bọc trong <template v-if="..."><template> không render ra thẻ thật.


5. v-for — render danh sách

<ul>
  <li v-for="todo in todos" :key="todo.id">
    {{ todo.text }}
  </li>
</ul>

Có index và lặp qua object:

<li v-for="(item, index) in items" :key="item.id">{{ index }} — {{ item.name }}</li>
<li v-for="(value, key) in user" :key="key">{{ key }}: {{ value }}</li>

Vì sao bắt buộc :key

key cho Vue một danh tính ổn định cho mỗi phần tử để tái dùng và sắp xếp lại DOM đúng khi danh sách đổi, thay vì vá nhầm phần tử. Đừng dùng index làm key nếu danh sách có thể chèn/xóa/sắp xếp — nó gây bug state lệch (ví dụ input giữ giá trị ở dòng sai). Luôn dùng id ổn định, duy nhất.

Không đặt v-ifv-for trên cùng một thẻ — thứ tự ưu tiên dễ gây nhầm. Lọc trước bằng computed rồi v-for trên kết quả đã lọc.


6. v-on — xử lý sự kiện

v-on, viết tắt @, gắn sự kiện DOM:

<button @click="count++">Inline</button>
<button @click="handleClick">Tham chiếu method</button>
<button @click="handleClick($event, item.id)">Truyền tham số + event</button>

Event modifier

Vue cho modifier để khỏi viết boilerplate quen thuộc:

<form @submit.prevent="onSubmit">…</form>       <!-- event.preventDefault() -->
<div @click.stop="onClick">…</div>               <!-- event.stopPropagation() -->
<div @click.self="onSelf">…</div>                <!-- chỉ khi click chính nó -->
<input @keyup.enter="search" />                  <!-- chỉ phím Enter -->
<button @click.once="init">Chỉ chạy 1 lần</button>

.prevent.enter là hai cái bạn dùng nhiều nhất — form và ô tìm kiếm.


7. v-model — ràng buộc hai chiều

Với input, bạn thường muốn state ↔ giá trị input đồng bộ cả hai chiều. v-model làm đúng việc đó:

<script setup>
import { ref } from 'vue';
const name = ref('');
const agreed = ref(false);
const role = ref('user');
</script>

<template>
  <input v-model="name" placeholder="Tên" />
  <input type="checkbox" v-model="agreed" />
  <select v-model="role">
    <option value="user">User</option>
    <option value="admin">Admin</option>
  </select>
  <p>{{ name }} — {{ agreed }} — {{ role }}</p>
</template>

v-model thực chất là đường tắt của :value + @input (tự chọn đúng property/event theo loại input). Modifier hữu ích:

<input v-model.trim="name" />     <!-- tự trim khoảng trắng -->
<input v-model.number="age" />    <!-- ép về số -->
<input v-model.lazy="bio" />      <!-- sync ở change thay vì input -->

Ta sẽ dựng v-model cho component tùy biến ở Phần 6.


8. Bài tập

1. Khi nào nên dùng v-show thay vì v-if?

Lời giải

Khi phần tử bị toggle thường xuyên (tab, dropdown) — v-show chỉ đổi CSS display, rẻ hơn việc v-if dựng/hủy lại DOM mỗi lần.

2. Vì sao không nên dùng index của v-for làm :key cho danh sách có thể sắp xếp lại?

Lời giải

index đổi khi danh sách chèn/xóa/sắp xếp, nên Vue ánh xạ sai phần tử cũ ↔ mới, dẫn tới tái dùng DOM nhầm (state như giá trị input dính ở dòng sai). Dùng id ổn định, duy nhất.

3. Viết một ô input có nút xóa, đồng bộ hai chiều với biến query, và submit bằng phím Enter.

Lời giải
<input v-model.trim="query" @keyup.enter="search" />
<button @click="query = ''">Xóa</button>

Điểm chính

  • {{ }} cho text, v-bind/: cho attribute, hỗ trợ object/array cho :class:style.
  • v-if gỡ khỏi DOM (điều kiện hiếm đổi); v-show toggle CSS (bật/tắt thường xuyên).
  • v-for luôn cần :key ổn định, duy nhất — đừng dùng index cho danh sách động.
  • v-on/@ + modifier (.prevent, .stop, .enter) cắt boilerplate sự kiện.
  • v-model = ràng buộc hai chiều, với modifier .trim / .number / .lazy.

Phần tiếp theo

Bạn đã biết ràng buộc state ra UI. Phần 3 — Reactivity deep dive mở nắp capo: ref vs reactive, computed được cache thế nào, watch vs watchEffect, các bẫy reactivity (mất reactivity khi destructure, toRefs), và shallowRef/readonly — để bạn không chỉ dùng được mà còn giải thích được vì sao UI cập nhật.