點燈坊

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

使用 Vue Router 實作 Todo List

Sam Xiao's Avatar 2024-02-28

將 Todo List 以 Vue Router 實現,將更接近實務上的使用。

Todo List

todolist001

All

homework02

Active

homework03

Completed

homework04

  • 加上以下 3 個 route

    • /:name 為 all,component 為 TodoList view
    • /active:name 為 active,component 為 TodoList view
    • /completed:name 為 completed,component 為 TodoList view
  • All button 改切換 / route

  • Active button 改切換 /active button

  • Completed button 改切換 /completed button

Routes

routes/index.js

import { createRouter, createWebHistory } from 'vue-router'
import TodoView from '../views/TodoView.vue'

export let router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'all',
      component: TodoView
    },
    {
      path: '/active',
      name: 'active',
      component: TodoView
    },
    {
      path: '/completed',
      name: 'completed',
      component: TodoView
    }
  ]
})

Line 2

import TodoView from '../views/TodoView.vue'
  • 引用 TodoView view,將在 route 內指定 view

Line 4

export let router = createRouter()
  • createRouter():建立 router object

Line 5

history: createWebHistory(),
  • createWebHistory():Vue Router 使用 HTML 5 Mode

Line 6

routes: [
  {
    path: '/',
    name: 'all',
    component: TodoView
  },
  {
    path: '/active',
    name: 'active',
    component: TodoView
  },
  {
    path: '/completed',
    name: 'completed',
    component: TodoView
  }
]
  • path:設定 URL
  • name:設定 router 邏輯名稱,Vue 內將以此名稱跳轉
  • component:設定 route 所對應的的 view

App

App.vue

<template>
  <RouterView />
</template>

<script setup>
import { RouterView } from 'vue-router'
</script>

Line 2

<RouterView />
  • <RouterView> 為 Vue Router 內建的 component,Vue Router 將 render 此 component

View

views/TodoView.vue

<template>
  <TodoList />
</template>

<script setup>
import TodoList from '@/components/TodoList.vue'
</script>

Line 2

<TodoList />
  • 使用 <TodoList> component

Component

TodoList.vue

<template>
  <TodoHeader v-model="newTodo" @addTodo="onAddTodo" />
  <TodoMain
    :dataSrc="filteredTodos"
    @completedTodo="onCompletedTodo"
    @saveTodo="onSaveTodo"
    @delTodo="onDelTodo"
  />
  <TodoFooter
    :dataSrc="todos"
    @filterAll="onFilterAll"
    @filterActive="onFilterActive"
    @filterCompleted="onFilterCompleted"
    @clearCompleted="onClearCompleted"
    >items left
  </TodoFooter>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import TodoHeader from '@/components/TodoHeader.vue'
import TodoMain from '@/components/TodoMain.vue'
import TodoFooter from '@/components/TodoFooter.vue'
import { getTodos, addTodo, saveTodo, delTodo } from '@/api/todos.js'
import { useRoute, useRouter } from 'vue-router'

let route = useRoute()
let { push } = useRouter()

let todos = ref([])
let newTodo = ref('')

let active = computed(() => todos.value.filter((todo) => !todo.isCompleted))
let completed = computed(() => todos.value.filter((todo) => todo.isCompleted))
let filteredTodos = computed(
  () =>
    ({
      all: todos.value,
      active: active.value,
      completed: completed.value
    })[route.name]
)

onMounted(async () => (todos.value = await getTodos()))

let onAddTodo = async (val) => {
  if (val === '') return

  await addTodo(val)
  todos.value = await getTodos()
  newTodo.value = ''
}

let onCompletedTodo = async (item) => {
  await saveTodo(item)
}

let onSaveTodo = async (item) => {
  await saveTodo(item)
}

let onDelTodo = async (item) => {
  await delTodo(item)
  todos.value = await getTodos()
}

let onClearCompleted = async () => {
  for (let item of completed.value) {
    await delTodo(item)
  }

  todos.value = await getTodos()
}

let onFilterAll = () => push({ name: 'all' })
let onFilterActive = () => push({ name: 'active' })
let onFilterCompleted = () => push({ name: 'completed' })
</script>

Line 25

import { useRoute, useRouter } from 'vue-router'
  • 從 Vue Router 引用 useRoute()useRouter()

Line 27

let route = useRoute()
let { push } = useRouter()
  • useRoute():取得目前 route object
  • useRouter():取得 Vue Router Object,從該 object 取出 push()

Line 35

let filteredTodos = computed(
  () =>
    ({
      all: todos.value,
      active: active.value,
      completed: completed.value
    })[route.name]
)
  • 取得目前 route object 的 name
  • name filter 適當 todos

Line 75

let onFilterAll = () => push({ name: 'all' })
let onFilterActive = () => push({ name: 'active' })
let onFilterCompleted = () => push({ name: 'completed' })
  • <TodoFooter> component 所傳出的 FilterAllFilterActiveFilterCompleted event 改直接使用 push() 切換 route

Conclusion

  • 不一定一個 route 就要對應一個 view,也可以多個 route 對應一個 view,只要 route 的 name 不同即可