☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
vue 初學

📗 Vue 3 基礎語法:模板、資料綁定與事件

📌 Composition API vs Options API

Vue 3 提供了兩種寫法風格。本教程使用推薦的 Composition API

記住:不管哪種 API,底層都是 JavaScript。Vue 只是幫你封裝了響應式系統。

// ❌ Options API(Vue 2 風格,仍支援但不推薦)
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() { this.count++ }
  }
}

// ✅ Composition API + <script setup>(Vue 3 推薦)
// 更接近原生 JavaScript 的寫法
import { ref } from 'vue'
const count = ref(0)
function increment() { count.value++ }

📌 模板語法:插值與指令

文字插值 {{ }}

<template>
  <!-- 雙大括號:將 JS 表達式渲染為文字 -->
  <p>你好,{{ name }}!</p>
  <p>1 + 1 = {{ 1 + 1 }}</p>
  <p>大寫:{{ name.toUpperCase() }}</p>
  <!-- ⚠️ 只能用「表達式」,不能用「語句」 -->
  <!-- ❌ {{ if (ok) { return 'yes' } }} -->
  <!-- ✅ {{ ok ? 'yes' : 'no' }} -->
</template>

<script setup>
import { ref } from 'vue'
// ref() 是 Vue 的 API,不是原生 JS!
// 它把普通值包裝成「響應式物件」
const name = ref('小明')
</script>

屬性綁定 v-bind

<template>
  <!-- v-bind 綁定 HTML 屬性 -->
  <img v-bind:src="imageUrl" v-bind:alt="imageAlt" />

  <!-- 簡寫:用冒號 : 代替 v-bind -->
  <img :src="imageUrl" :alt="imageAlt" />

  <!-- 動態 class 和 style -->
  <div :class="{ active: isActive, 'text-danger': hasError }">
    條件式 class
  </div>

  <div :style="{ color: textColor, fontSize: fontSize + 'px' }">
    動態樣式
  </div>
</template>

<script setup>
import { ref } from 'vue'

const imageUrl = ref('https://example.com/logo.png')
const imageAlt = ref('Logo')
const isActive = ref(true)
const hasError = ref(false)
const textColor = ref('blue')
const fontSize = ref(16)
</script>

事件處理 v-on / @

<template>
  <!-- v-on 監聽事件 -->
  <button v-on:click="handleClick">點我 (完整寫法)</button>

  <!-- 簡寫:用 @ 代替 v-on -->
  <button @click="count++">直接寫表達式:{{ count }}</button>
  <button @click="handleClick">呼叫函式</button>

  <!-- 帶參數 -->
  <button @click="greet('小明')">打招呼</button>

  <!-- 事件修飾符 -->
  <form @submit.prevent="onSubmit">
    <!-- .prevent = preventDefault() -->
    <input @keyup.enter="onEnter" placeholder="按 Enter" />
    <button type="submit">送出</button>
  </form>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)

// 這些就是普通的 JavaScript 函式
function handleClick() {
  console.log('被點擊了!')
  count.value++
}

function greet(name) {
  alert(`你好,${name}!`)
}

function onSubmit() {
  console.log('表單送出')
}

function onEnter() {
  console.log('按了 Enter')
}
</script>

📌 響應式資料:ref() 和 reactive()

ref() — 包裝任意值

<script setup>
import { ref } from 'vue'

// ref() 包裝基本型別
const count = ref(0)          // 數字
const name = ref('小明')       // 字串
const isReady = ref(false)     // 布林值
const items = ref([1, 2, 3])   // 陣列
const user = ref({ name: '小明', age: 25 }) // 物件

// ⚠️ 在 <script> 中存取要加 .value
console.log(count.value)   // 0
count.value++              // 修改值
console.log(count.value)   // 1

// ✅ 在 <template> 中不需要 .value(Vue 自動解包)
// <p>{{ count }}</p>  ← 直接用,不用 count.value
</script>

reactive() — 包裝物件

<script setup>
import { reactive } from 'vue'

// reactive() 只能包裝物件或陣列
const state = reactive({
  count: 0,
  user: { name: '小明', age: 25 },
  todos: []
})

// 不需要 .value!直接存取
state.count++
state.user.name = '小華'
state.todos.push('學 Vue')

// ⚠️ 不能解構!會失去響應性
// ❌ const { count } = state  // count 不會是響應式的
// ✅ 用 toRefs 解構
import { toRefs } from 'vue'
const { count } = toRefs(state)  // 現在 count 是 ref
</script>

ref vs reactive 怎麼選?

ref()      → 適合基本型別(數字、字串、布林)
reactive() → 適合物件、表單資料等複合型別
推薦:統一用 ref(),因為更一致、不容易出錯

📌 計算屬性 computed

<template>
  <div>
    <p>原價:${{ price }}</p>
    <p>折扣後:${{ discountedPrice }}</p>
    <p>訊息:{{ statusMessage }}</p>

    <input v-model="firstName" placeholder="名" />
    <input v-model="lastName" placeholder="姓" />
    <p>全名:{{ fullName }}</p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const price = ref(1000)
const discount = ref(0.8)

// computed 會自動追蹤依賴的 ref
// 當 price 或 discount 改變時,自動重新計算
const discountedPrice = computed(() => {
  return Math.round(price.value * discount.value)
})

// computed 有快取!只有依賴變化時才重新計算
// 比起 methods,效能更好
const statusMessage = computed(() => {
  return price.value > 500 ? '高價商品' : '平價商品'
})

const firstName = ref('')
const lastName = ref('')

// 可讀可寫的 computed
const fullName = computed({
  get: () => `${lastName.value}${firstName.value}`,
  set: (val) => {
    lastName.value = val[0] || ''
    firstName.value = val.slice(1) || ''
  }
})
</script>

📌 條件渲染與列表渲染

<template>
  <!-- v-if / v-else-if / v-else:條件渲染 -->
  <div v-if="score >= 90">🏆 優秀!</div>
  <div v-else-if="score >= 60">✅ 及格</div>
  <div v-else>❌ 不及格</div>

  <!-- v-show:用 CSS display 切換(頻繁切換用這個) -->
  <div v-show="isVisible">我可以被顯示/隱藏</div>

  <!-- v-for:列表渲染 -->
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      {{ index + 1 }}. {{ item.name }} - ${{ item.price }}
    </li>
  </ul>

  <!-- v-for 搭配物件 -->
  <div v-for="(value, key) in userInfo" :key="key">
    {{ key }}: {{ value }}
  </div>
</template>

<script setup>
import { ref } from 'vue'

const score = ref(85)
const isVisible = ref(true)

const items = ref([
  { id: 1, name: '蘋果', price: 30 },
  { id: 2, name: '香蕉', price: 15 },
  { id: 3, name: '橘子', price: 25 }
])

const userInfo = ref({
  name: '小明',
  age: 25,
  city: '台北'
})
</script>

📌 完整範例:計數器 + 待辦清單

<template>
  <div class="app">
    <h1>Vue 3 練習</h1>

    <!-- 計數器 -->
    <section>
      <h2>計數器</h2>
      <p>目前數字:{{ count }}</p>
      <p>是否為偶數:{{ isEven ? '是' : '否' }}</p>
      <button @click="count--">-1</button>
      <button @click="count++">+1</button>
      <button @click="count = 0">歸零</button>
    </section>

    <!-- 待辦清單 -->
    <section>
      <h2>待辦清單 ({{ remainingCount }} 項未完成)</h2>
      <form @submit.prevent="addTodo">
        <input v-model="newTodo" placeholder="輸入待辦事項..." />
        <button type="submit" :disabled="!newTodo.trim()">新增</button>
      </form>

      <ul>
        <li v-for="todo in todos" :key="todo.id"
            :class="{ done: todo.completed }">
          <input type="checkbox" v-model="todo.completed" />
          <span>{{ todo.text }}</span>
          <button @click="removeTodo(todo.id)">🗑️</button>
        </li>
      </ul>

      <p v-if="todos.length === 0">還沒有待辦事項 🎉</p>
    </section>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

// 計數器
const count = ref(0)
const isEven = computed(() => count.value % 2 === 0)

// 待辦清單
const newTodo = ref('')
const todos = ref([])
let nextId = 1

function addTodo() {
  if (!newTodo.value.trim()) return
  todos.value.push({
    id: nextId++,
    text: newTodo.value.trim(),
    completed: false
  })
  newTodo.value = ''
}

function removeTodo(id) {
  todos.value = todos.value.filter(t => t.id !== id)
}

const remainingCount = computed(() => {
  return todos.value.filter(t => !t.completed).length
})
</script>

<style scoped>
.done span {
  text-decoration: line-through;
  color: #999;
}
</style>

💡 小提醒

  • ref() 在 JS 中要用 .value,在 template 中不用
  • computed 有快取,methods 沒有——需要快取的用 computed
  • v-if 是真正移除 DOM,v-show 只是 CSS 隱藏
  • v-for 一定要加 :key,用唯一值(不要用 index)
  • 這些語法(refcomputedv-model)都是 Vue 封裝的 API,不是 JS 原生的!

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊