點燈坊

失くすものさえない今が強くなるチャンスよ

Hugo X Vue 3 實現 Todo List

Sam Xiao's Avatar 2024-01-28

Hugo 雖然常搭配 Petite-vue,但只能使用 reactive(),也沒 computed() 可用,若搭配 Vue 3 就能使用 ref()computed()

Version

Vue 3.4

Hugo X Vue 3

todolist001

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    {{ $title := "Hugo X Vue 3 Lab" }}
    <title>{{ $title }}</title>
  </head>
  <body id="app">
    <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">[[ item.todo ]]</span>
          <span v-else><input type="text" v-model="item.todo" /></span>
          <span v-if="!item.isEdit"><button @click="item.isEdit = true">edit</button></span>
          <span v-else><button @click="item.isEdit = false">save</button></span>
          <button @click="onDelete(item)">delete</button>
        </li>
      </ul>
    </div>
    <div>
      <span> [[ active.length ]] items left</span>
      <button @click="onAll">All</button>
      <button @click="onActive">Active</button>
      <button @click="onCompleted">Completed</button>
      <button v-if="completed.length" @click="onClearCompleted">Clear Completed</button>
    </div>
  </body>
</html>

<script type="module">
import { createApp, ref, computed } from './js/vue.esm-browser.prod.js'

let newTodo = ref('')
let todos = ref([])
let filter = ref(() => true)

let active = computed(() => todos.value.filter(todo => !todo.isCompleted))
let completed = computed(() => todos.value.filter(todo => todo.isCompleted))
let filteredTodos = computed(() => todos.value.filter(filter.value))

let onAdd = () => {
  if (!newTodo.value) return

  todos.value = [
    ...todos.value,
    {
      id: Symbol(),
      isCompleted: false,
      isEdit: false,
      todo: newTodo.value
    }
  ]
  newTodo.value = ''
}

let onDelete = item => todos.value = todos.value.filter(todo => todo.id !== item.id)

let onAll = () => filter.value = () => true

let onActive = () => filter.value = todo => !todo.isCompleted

let onCompleted = () => filter.value = todo => todo.isCompleted

let onClearCompleted = () => todos.value = todos.value.filter(todo => !todo.isCompleted)

createApp({
  delimiters: ['[[', ']]'],
  setup: () => ({
    newTodo,
    todos,
    filter,
    active,
    completed,
    filteredTodos,
    onAdd,
    onDelete,
    onAll,
    onActive,
    onCompleted,
    onClearCompleted
  })
}).mount('#app')
</script>

Line 11

<input type="text" v-model="newTodo" />
  • v-model:將欲輸入的新 todo 以 雙向綁定newTodo

Line 12

<button @click="onAdd">Add</button>
  • 將 Add 的 click event 指定到 onAdd()

Line 15

<ul>
  <li v-for="(item, i) in filteredTodos" :key="i">
    <span>[[ item.todo ]]</span>
  </li>
</ul>
  • v-for :列舉 filteredTodos()

Line 17

<input type="checkbox" v-model="item.isCompleted" />
  • v-model:將 checkbox 以 雙向綁定item.isCompleted

Line 18

<span v-if="!item.isEdit">[[ item.todo ]]</span>
<span v-else><input type="text" v-model="item.todo" /></span>
  • v-if:若不是 編輯模式,則直接顯示
  • v-else:否則顯示 <input>

Line 20

<span v-if="!item.isEdit"><button @click="item.isEdit = true">edit</button></span>
<span v-else><button @click="item.isEdit = false">save</button></span>
  • v-if:若不是 編輯模式,則顯示 edit button
  • v-else:否則顯示 save button

Line 22

<button @click="onDelete(item)">delete</button>
  • 將 delete 的 click event 指定到 onDelete()

Line 27

<span> [[ active.length ]] items left</span>
  • 顯示目前尚未完成的 todo 筆數

Line 28

<button @click="onAll">All</button>
  • 顯示所有 todo,將 click event 指定到 onAll()

Line 29

<button @click="onActive">Active</button>
  • 顯示尚未完成 todo,將 click event 指定到 onActive()

Line 30

<button @click="onCompleted">Completed</button>
  • 顯示已完成 todo,將 click event 指定到 onCompleted()

Line 31

<button v-if="completed.length" @click="onClearCompleted">Clear Completed</button>
  • v-if:若有已完成 todo,則顯示 Clear Completed button

Line 39

let newTodo = ref('')
let todos = ref([])
let filter = ref(() => true)
  • 使用 ref() 宣告 state
  • newTodo:儲存新輸入的 todo
  • todos:儲存所有 todo
  • filter:儲存 AllActive
  • Completed 所需要的 filter function

Line 43

let active = computed(() => todos.value.filter(todo => !todo.isCompleted))
  • active Computed:當 todo 改變時,active Computed 會 reactive 跟著改變,只顯示所有 未完成 todo

Line 44

let completed = computed(() => todos.value.filter(todo => todo.isCompleted))
  • completed Computed:當 todo 改變時,completed Computed 會 reactive 跟著改變,只顯示所有 已完成 todo

Line 45

let filteredTodos = computed(() => todos.value.filter(filter.value))
  • filteredTodos Computed:當 todo 改變時,filteredTodos Computed 會 reactive 跟著改變,只顯示所有目前 filter function 所指定的 todo

Line 47

let onAdd = () => {
  if (!newTodo.value) return

  todos.value = [
    ...todos.value,
    {
      id: Symbol(),
      isCompleted: false,
      isEdit: false,
      todo: newTodo.value
    }
  ]
  newTodo.value = ''
}
  • 使用 push 新增 todo
  • id 使用 Symbol() 實現 UUID

Line 62

let onDelete = item => todos.value = todos.value.filter(todo => todo.id !== item.id)
  • 使用 filter() 取代 splice() 刪除 Array 中的 element

Line 64

let onAll = () => filter.value = () => true
  • 顯示所有 todo,直接提供 filter function

Line 66

let onActive = () => filter.value = todo => !todo.isCompleted
  • 顯示尚未完成 todo,直接提供 filter function

Line 68

let onCompleted = () => filter.value = todo => todo.isCompleted
  • 顯示已完成 todo,直接提供 filter function

Line 70

let onClearCompleted = () => todos.value = todos.value.filter(todo => !todo.isCompleted)
  • 使用 filter() 取代 splice() 刪除 Array 中的 element

Line 72

createApp({
  delimiters: ['[[', ']]'],
  setup: () => ({
    newTodo,
    todos,
    filter,
    active,
    completed,
    filteredTodos,
    onAdd,
    onDelete,
    onAll,
    onActive,
    onCompleted,
    onClearCompleted
  })
}).mount('#app')
  • 使用 setup() 整合所有 state 與 function
  • $delimiters:為了避開 Go template 的 delimiter

Conclusion

  • 在 Vue 3 可直接使用 ref()computed(),DX 體驗較 Petite-vue 佳
  • 由於沒有 Script Setup,只能在 setup() 中整合所有 state 與 function