iT邦幫忙

2025 iThome 鐵人賽

DAY 14
0
Vue.js

Vue3.6 的革新:深入理解 Composition API系列 第 14

Day 14: 小專案實作:整合已學知識,製作簡單應用

  • 分享至 

  • xImage
  •  

去年鐵人賽我做了一個任務管理系統來練後端,今年決定延續這個作品,挑戰把前兩週學到的 Vue 3 知識整合到一個實際的小專案。
這不只是 CRUD,而是希望在一個小範例中一次練熟 Composition API、資料監控、props/emit 等核心觀念。

  • Composition API :把邏輯拆開、模組化,未來要擴充功能也更好維護。
  • ref/reactive (Proxy) :用 Proxy 追蹤資料變化,直接操作物件就能觸發畫面更新,少了 .setState() 之類的心智負擔。
  • computed:計算完成率。
  • watch / watchEffectwatch 更適合指定監聽、watchEffect 則適合快速處理副作用,像自動顯示完成率就是最佳案例。
  • props / emit:在子元件間傳遞資料。
  • v-model:在父子元件間做雙向綁定。

以下分享我如何一步步完成這個整合練習,另外也可以觀察到,在 React 需要額外 state 管理工具的東西,Vue 只靠 computedwatchEffect 就能達成具體可以實作如下功能:

  • 新增任務 C:建立一個表單來新增任務,並利用 v-model 來實現表單資料的雙向繫結。
  • 任務展示 R:使用資料監控技術來顯示任務的列表,並能即時反映對資料的更改。
  • 編輯任務 U:支援編輯已有的任務,使用資料監控來更新任務的狀態和內容。
  • 刪除任務 D:可以刪除任務,並將刪除操作與資料更新進行監控,實現資料的即時更新。
  • 任務過濾:提供過濾功能以過濾完成或未完成的任務,進一步使用資料監控來展示過濾後的清單。

專案結構 (Vite + Vue3)


src/
 ├─ App.vue           # 主應用
 ├─ components/
 │   ├─ TodoInput.vue # 新增待辦輸入框
 │   ├─ TodoItem.vue  # 單個待辦項目
 │   └─ TodoStats.vue # 統計資訊

App.vue


<script setup lang="ts">
import { ref, computed, watch, watchEffect } from 'vue'
import TodoInput from './components/TodoInput.vue'
import TodoItem from './components/TodoItem.vue'
import TodoStats from './components/TodoStats.vue'

// 響應式資料
const todos = ref<{ id: number; text: string; done: boolean }[]>([])

// 新增待辦
function addTodo(text: string) {
 todos.value.push({
   id: Date.now(),
   text,
   done: false
 })
}

// 切換完成狀態
function toggleTodo(id: number) {
 const t = todos.value.find(t => t.id === id)
 if (t) t.done = !t.done
}

// 移除待辦
function removeTodo(id: number) {
 todos.value = todos.value.filter(t => t.id !== id)
}

// computed: 已完成比例
const completedRatio = computed(() => {
 if (todos.value.length === 0) return 0
 const done = todos.value.filter(t => t.done).length
 return Math.round((done / todos.value.length) * 100)
})

// watch:當 todos 有變化時記錄 log
watch(todos, (newVal) => {
 console.log('待辦清單更新 =>', newVal)
}, { deep: true })

// watchEffect:副作用 → 自動顯示當前完成率
watchEffect(() => {
 console.log(`當前完成率 => ${completedRatio.value}%`)
})
</script>

<template>
 <div class="p-6 max-w-md mx-auto space-y-4">
   <h1 class="text-2xl font-bold">Todo List</h1>

   <!-- 新增輸入框 -->
   <TodoInput @add="addTodo" />

   <!-- 待辦清單 -->
   <div class="space-y-2">
     <TodoItem
       v-for="t in todos"
       :key="t.id"
       v-model:done="t.done"
       :text="t.text"
       @remove="removeTodo(t.id)"
     />
   </div>

   <!-- 統計資訊 -->
   <TodoStats :total="todos.length" :ratio="completedRatio" />
 </div>
</template>

components/TodoInput.vue


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

const text = ref('')
const emit = defineEmits<{
  (e: 'add', text: string): void
}>()

function add() {
  if (text.value.trim() === '') return
  emit('add', text.value)
  text.value = ''
}
</script>

<template>
  <div class="flex space-x-2">
    <input
      v-model="text"
      @keyup.enter="add"
      class="border rounded px-2 py-1 flex-1"
      placeholder="輸入待辦..."
    />
    <button @click="add" class="bg-blue-500 text-white px-3 py-1 rounded">
      新增
    </button>
  </div>
</template>

components/TodoItem.vue


<script setup lang="ts">
const props = defineProps<{
  text: string
  done: boolean
}>()

const emit = defineEmits<{
  (e: 'update:done', value: boolean): void
  (e: 'remove'): void
}>()

// 使用 v-model:done 雙向綁定
</script>

<template>
  <div class="flex items-center space-x-2 border rounded px-2 py-1">
    <input
      type="checkbox"
      :checked="props.done"
      @change="emit('update:done', !props.done)"
    />
    <span :class="{ 'line-through text-gray-500': props.done }">
      {{ props.text }}
    </span>
    <button @click="emit('remove')" class="text-red-500 ml-auto">x</button>
  </div>
</template>

components/TodoStats.vue


<script setup lang="ts">
const props = defineProps<{
  total: number
  ratio: number
}>()
</script>

<template>
  <div class="mt-4 text-gray-600">
    <p>總待辦數:{{ props.total }}</p>
    <p>完成率:{{ props.ratio }}%</p>
  </div>
</template>

小結


這次的小專案就像是 Vue 3 的縮影:
資料一動,畫面自動同步,從資料響應到元件拆分、雙向綁定,再到副作用監控,都一次練到。

下一步我要進入 Composable 的世界。
它和 Vue 3.6 的新底層「Alien Signals」沒有直接關係,但因為新版引擎更快更省記憶體,寫 composable 也能無痛獲得效能紅利唷!


上一篇
Day 13: 瞭解 v-model 的實現細節與案例
系列文
Vue3.6 的革新:深入理解 Composition API14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言