Vue 3 提供 Composition API 後,將以完全不同型態實作 Todo List。
Version
Vue 3.4
Todo List
<template>
<div>
<input type="text" v-model="newTodo" />
<button @click="onAdd">Add</button>
</div>
<div>
<ul>
<li v-for="(item, i) in filteredTodos" :key="i">
<input type="checkbox" v-model="item.isCompleted" />
<span v-if="!item.isEdit">
<span>{{ item.todo }}</span>
<button @click="onEdit(item)">edit</button>
<button @click="onDelete(item)">delete</button>
</span>
<span v-else>
<input type="text" v-model="item.todo_" />
<button @click="onSave(item)">save</button>
<button @click="onCancel(item)">cancel</button>
</span>
</li>
</ul>
</div>
<div>
<span> {{ active.length }} items left</span>
<button @click="onFilterAll">All</button>
<button @click="onFilterActive">Active</button>
<button @click="onFilterCompleted">Completed</button>
<button v-if="completed.length" @click="onClearCompleted">Clear Completed</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
let newTodo = ref('')
let todos = ref([])
let filter = ref('all')
let filteredTodos = computed(() => {
return {
all: todos.value,
active: active.value,
completed: completed.value
}[filter.value]
})
let active = computed(() => {
return todos.value.filter((todo) => !todo.isCompleted)
})
let completed = computed(() => {
return todos.value.filter((todo) => todo.isCompleted)
})
let onAdd = () => {
if (newTodo.value === '') return
todos.value.push({
id: Symbol(),
isCompleted: false,
todo: newTodo.value
})
newTodo.value = ''
}
let onEdit = (item) => {
item.isEdit = true
item.todo_ = item.todo
}
let onDelete = (item) => {
let index = todos.value.findIndex((todo) => todo.id === item.id)
todos.value.splice(index, 1)
}
let onSave = (item) => {
item.isEdit = false
item.todo = item.todo_
}
let onCancel = (item) => {
item.isEdit = false
}
let onFilterAll = () => {
filter.value = 'all'
}
let onFilterActive = () => {
filter.value = 'active'
}
let onFilterCompleted = () => {
filter.value = 'completed'
}
let onClearCompleted = () => {
completed.value.forEach((item) => {
let index = todos.value.findIndex((todo) => todo.id === item.id)
todos.value.splice(index, 1)
})
}
</script>
Line 3
<input type="text" v-model="newTodo" />
v-model
:將輸入的新 todo 綁定到newTodo
state
Line 4
<button @click="onAdd">Add</button>
- 新增一筆 todo,並將
click
event 指定到onAdd()
Line 8
<ul>
<li v-for="(item, i) in filteredTodos" :key="i">
<span>{{ item.todo }}</span>
</li>
</ul>
v-for
:列舉filteredTodos
computed
Line 9
<input type="checkbox" v-model="item.isCompleted" />
v-model
:將 checkbox 綁定到item.isCompleted
Line 10
<span v-if="!item.isEdit">
<span>{{ item.todo }}</span>
<button @click="onEdit(item)">edit</button>
<button @click="onDelete(item)">delete</button>
</span>
v-if
:若是非編輯模式
,則直接顯示 todo- Button 此時為
edit
與delete
Line 15
<span v-else>
<input type="text" v-model="item.todo_" />
<button @click="onSave(item)">save</button>
<button @click="onCancel(item)">cancel</button>
</span>
v-else
:若是編輯模式
,則以<input>
顯示 todo,注意此時v-model
綁定到item.todo_
而非item.todo
,此為儲存暫時
的 todo,主要為了cancel
所用- Button 此時為
save
與cancel
Line 24
<span> {{ active.length }} items left</span>
- 顯示目前尚未完成的 todo 筆數
Line 25
<button @click="onFilterAll">All</button>
- 顯示所有 todo,並將
click
event 指定到onFilterAll()
Line 26
<button @click="onFilterActive">Active</button>
- 顯示尚未完成 todo,並將
click
event 指定到onFilterActive()
Line 27
<button @click="onFilterCompleted">Completed</button>
- 顯示已完成 todo,並將
click
event 指定到onFilterCompleted()
Line 28
<button v-if="completed.length" @click="onClearCompleted">Clear Completed</button>
v-if
:若有已完成 todo,則顯示Clear Completed
button- 將
click
event 指定到onClearCompleted()
Line 35
let newTodo = ref('')
let todos = ref([])
newTodo
state:儲存新輸入的 todotodos
state:儲存所有 todo
Line 38
let filter = ref('all')
let filteredTodos = computed(() => {
return {
all: todos.value,
active: active.value,
completed: completed.value
}[filter.value]
})
filter
state:儲存All
、Active
與Completed
button 所需的 statefilteredTodos
computed:當todo
state 改變時,filteredTodos
computed 會 reactive 跟著改變,只顯示根據目前filter
state 所指定的 todo
Line 47
let active = computed(() => {
return todos.value.filter(todo => !todo.isCompleted)
})
active
computed:當todo
state 改變時,active
computed 會 reactive 跟著改變,只顯示所有未完成
todo
Line 51
let completed = computed(() => {
return todos.value.filter(todo => todo.isCompleted)
})
completed
computed:當todo
state 改變時,completed
computed 會 reactive 跟著改變,只顯示所有已完成
todo
Line 55
let onAdd = () => {
if (newTodo.value === '') return
todos.value.push({
id: Symbol(),
isCompleted: false,
todo: newTodo.value
})
newTodo.value = ''
}
- 新增一筆 todo
- 使用
push
新增todo
array id
使用Symbol()
實現 UUID
Line 66
let onEdit = item => {
item.isEdit = true
item.todo_ = item.todo
}
- 編輯一筆 todo
- 在 item 下新增
isEdit
property 為true
表示為編輯模式
,此時 button 會改為顯示save
與cancel
- 在 item 下新增
todo_
property 作為臨時 state,並將目前toto
值寫入todo_
,作為綁定<input>
用
由於 JavaScript 為
動態語言
,我們可對 Object 動態新增 property 作為臨時變數,此為 JavaScript 慣用手法
Line 71
let onDelete = item => {
let index = todos.value.findIndex(todo => todo.id === item.id)
todos.value.splice(index, 1)
}
- 刪除一筆 todo
- 若要使用
splice()
直接對todos
state 進行刪除,則必須找到要刪除 todo 的 index - 使用
findIndex()
由目前 todo 的id
找到要刪除 todo 的 index
Line 76
let onSave = item => {
item.isEdit = false
item.todo = item.todo_
}
- 儲存一筆 todo
- 將 item 的
isEdit
設定為false
表示為非編輯模式
,此時 button 會改為顯示edit
與delete
- 因為目前編輯的
<input>
綁定到item.todo_
,將資料從item.todo_
複製到item.todo
完成儲存
Line 81
let onCancel = item => {
item.isEdit = false
}
- 取消編輯一筆 todo
- 將 item 的
isEdit
設定為false
表示為非編輯模式
,此時 button 會改為顯示edit
與delete
亦可以使用
delete item.todo_
動態刪除_todo
property 或item.todo_ = ''
清空,也可以不處理,反正每次onEdit()
都會重新動態新增todo_
property
Line 85
let onFilterAll = () => {
filter.value = 'all'
}
- 指定
filter
state 為all
顯示所有 todo
Line 89
let onFilterActive = () => {
filter.value = 'active'
}
- 指定
filter
state 為active
顯示未完成 todo
Line 93
let onFilterCompleted = () => {
filter.value = 'completed'
}
- 指定
filter
state 為completed
顯示已完成 todo
Line 97
let onClearCompleted = () => {
completed.value.forEach(item => {
let index = todos.value.findIndex(todo => todo.id === item.id)
todos.value.splice(index, 1)
})
}
- 為了使用
splice()
刪除todo
state 中的 element,所以使用forEach()
根據已完成todo
一筆一筆尋找 index
Conclusion
filteredTodos
computed 為 Todo List 的關鍵,只要任何filter
state 條件改變,都會 reactive 跟著變動- 因為 JavaScript 為動態語言,常對 Object 動態新增 property 作為臨時變數,用完可不必處理,因為每次
onEdit()
都會重新動態新增 property - 為了使用
splice()
刪除 Array 中的 element,必須先取得 index