更新於 2024/10/13閱讀時間約 25 分鐘

EP36 - ex8. TodoMVC

印象中上過Udemy的線上課程
Todolist真的是很常拿來被當範例啊~
馬上來實作看看吧~ 官方範例
最後一個實用範例摟!Github
由於父子組件CSS互相干擾的情況,有做了些許變動,
style 裡頭import的話就是Global,所以使用scoped就無效,會影響到全局
於是就把css下載下來後放進src資料夾,才能把css應用在單一組件內。
<style scoped src="../../assets/todomvc-app.css">
/* @import "https://unpkg.com/todomvc-app-css@2.4.1/index.css"; */
</style><style scoped src="../../assets/todomvc-app.css">

App.vue

<!--
A fully spec-compliant TodoMVC implementation
https://todomvc.com/
-->

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

const STORAGE_KEY = 'vue-todomvc'

const filters = {
all: (todos) => todos,
active: (todos) => todos.filter((todo) => !todo.completed),
completed: (todos) => todos.filter((todo) => todo.completed)
}

// state
const todos = ref(JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'))
const visibility = ref('all')
const editedTodo = ref()

// derived state
const filteredTodos = computed(() => filters[visibility.value](todos.value))
const remaining = computed(() => filters.active(todos.value).length)

// handle routing
window.addEventListener('hashchange', onHashChange)
onHashChange()

// persist state
watchEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos.value))
})

function toggleAll(e) {
todos.value.forEach((todo) => (todo.completed = e.target.checked))
}

function addTodo(e) {
const value = e.target.value.trim()
if (value) {
todos.value.push({
id: Date.now(),
title: value,
completed: false
})
e.target.value = ''
}
}

function removeTodo(todo) {
todos.value.splice(todos.value.indexOf(todo), 1)
}

let beforeEditCache = ''
function editTodo(todo) {
beforeEditCache = todo.title
editedTodo.value = todo
}

function cancelEdit(todo) {
editedTodo.value = null
todo.title = beforeEditCache
}

function doneEdit(todo) {
if (editedTodo.value) {
editedTodo.value = null
todo.title = todo.title.trim()
if (!todo.title) removeTodo(todo)
}
}

function removeCompleted() {
todos.value = filters.active(todos.value)
}

function onHashChange() {
const route = window.location.hash.replace(/#\/?/, '')
if (filters[route]) {
visibility.value = route
} else {
window.location.hash = ''
visibility.value = 'all'
}
}
</script>

<template>
<section class="todoapp">
<header class="header">
<h1>Todos</h1>
<input class="new-todo" autofocus placeholder="What needs to be done?" @keyup.enter="addTodo">
</header>
<section class="main" v-show="todos.length">
<input id="toggle-all" class="toggle-all" type="checkbox" :checked="remaining === 0" @change="toggleAll">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li v-for="todo in filteredTodos" class="todo" :key="todo.id"
:class="{ completed: todo.completed, editing: todo === editedTodo }">
<div class="view">
<input class="toggle" type="checkbox" v-model="todo.completed">
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
<input v-if="todo === editedTodo" class="edit" type="text" v-model="todo.title"
@vue:mounted="({ el }) => el.focus()" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)"
@keyup.escape="cancelEdit(todo)">
</li>
</ul>
</section>
<footer class="footer" v-show="todos.length">
<span class="todo-count">
<strong>{{ remaining }}</strong>
<span>{{ remaining === 1 ? ' item' : ' items' }} left</span>
</span>
<ul class="filters">
<li>
<a href="#/all" :class="{ selected: visibility === 'all' }">All</a>
</li>
<li>
<a href="#/active" :class="{ selected: visibility === 'active' }">Active</a>
</li>
<li>
<a href="#/completed" :class="{ selected: visibility === 'completed' }">Completed</a>
</li>
</ul>
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
Clear completed
</button>
</footer>
</section>
</template>

<style scoped src="../../assets/todomvc-app.css">
/* @import "https://unpkg.com/todomvc-app-css@2.4.1/index.css"; */
</style><style scoped src="../../assets/todomvc-app.css">

這是一個完全符合規範的 TodoMVC 實現。TodoMVC 是一個展示各種 JavaScript 框架和庫如何實現相同 Todo 應用的網站。

<script setup>

import { ref, computed, watchEffect } from 'vue'

這段代碼在 Vue 3 中設置組件,使用 setup 語法糖。refcomputedwatchEffect 是 Vue 的響應式 API。

const STORAGE_KEY = 'vue-todomvc'

定義了存儲鍵,用於 localStorage 中保存 todo 項。

const filters = {
all: (todos) => todos,
active: (todos) => todos.filter((todo) => !todo.completed),
completed: (todos) => todos.filter((todo) => todo.completed)
}

定義了三個過濾器:allactivecompleted,分別用於篩選所有、未完成和已完成的 todo 項。

狀態管理

const todos = ref(JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'))
const visibility = ref('all')
const editedTodo = ref()

定義了三個響應式變量:todos 用於存儲所有的 todo 項,visibility 用於存儲當前的過濾狀態,editedTodo 用於存儲當前正在編輯的 todo 項。

派生狀態 - derived state

const filteredTodos = computed(() => filters[visibility.value](todos.value))
const remaining = computed(() => filters.active(todos.value).length)

定義了兩個計算屬性:filteredTodos 根據當前的過濾狀態過濾 todos,remaining 計算未完成的 todo 項數量。

路由處理

window.addEventListener('hashchange', onHashChange)
onHashChange()

監聽 URL 中的 hash 變化,以便根據 hash 更新過濾狀態。

狀態持久化

watchEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos.value))
})

監聽 todos 的變化,並將其保存到 localStorage 中,以便下次加載時恢復。

事件處理函數

function toggleAll(e) {
todos.value.forEach((todo) => (todo.completed = e.target.checked))
}

function addTodo(e) {
const value = e.target.value.trim()
if (value) {
todos.value.push({
id: Date.now(),
title: value,
completed: false
})
e.target.value = ''
}
}

function removeTodo(todo) {
todos.value.splice(todos.value.indexOf(todo), 1)
}

let beforeEditCache = ''
function editTodo(todo) {
beforeEditCache = todo.title
editedTodo.value = todo
}

function cancelEdit(todo) {
editedTodo.value = null
todo.title = beforeEditCache
}

function doneEdit(todo) {
if (editedTodo.value) {
editedTodo.value = null
todo.title = todo.title.trim()
if (!todo.title) removeTodo(todo)
}
}

function removeCompleted() {
todos.value = filters.active(todos.value)
}

function onHashChange() {
const route = window.location.hash.replace(/#\/?/, '')
if (filters[route]) {
visibility.value = route
} else {
window.location.hash = ''
visibility.value = 'all'
}
}

這些函數處理不同的事件,如切換所有項目完成狀態、添加新項目、刪除項目、編輯項目、取消編輯、完成編輯、刪除已完成項目以及處理 URL 中的 hash 變化。

<Template >

<template>
<section class="todoapp">
<header class="header">
<h1>Todos</h1>
<input class="new-todo" autofocus placeholder="What needs to be done?" @keyup.enter="addTodo">
</header>
<section class="main" v-show="todos.length">
<input id="toggle-all" class="toggle-all" type="checkbox" :checked="remaining === 0" @change="toggleAll">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li v-for="todo in filteredTodos" class="todo" :key="todo.id"
:class="{ completed: todo.completed, editing: todo === editedTodo }">
<div class="view">
<input class="toggle" type="checkbox" v-model="todo.completed">
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
<input v-if="todo === editedTodo" class="edit" type="text" v-model="todo.title"
@vue:mounted="({ el }) => el.focus()" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)"
@keyup.escape="cancelEdit(todo)">
</li>
</ul>
</section>
<footer class="footer" v-show="todos.length">
<span class="todo-count">
<strong>{{ remaining }}</strong>
<span>{{ remaining === 1 ? ' item' : ' items' }} left</span>
</span>
<ul class="filters">
<li>
<a href="#/all" :class="{ selected: visibility === 'all' }">All</a>
</li>
<li>
<a href="#/active" :class="{ selected: visibility === 'active' }">Active</a>
</li>
<li>
<a href="#/completed" :class="{ selected: visibility === 'completed' }">Completed</a>
</li>
</ul>
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
Clear completed
</button>
</footer>
</section>
</template>

這段模板代碼定義了應用的結構。包括標題、新建待辦事項的輸入框、待辦事項列表、過濾器和清除已完成項目的按鈕。

<Style>

<style scoped src="../../assets/todomvc-app.css">
/* @import "https://unpkg.com/todomvc-app-css@2.4.1/index.css"; */
</style>

這段代碼引用了 todomvc-app.css 樣式文件,以確保應用的外觀符合 TodoMVC 規範。

由於父組件仍會影響到子組件footer的呈現,所以就在上一層包了一個container讓子組件能順利排列。

<style scoped>
.todomvc-container {
display: flex;
flex-direction: column;
}
</style>
關於父子組件的CSS的影響還真的要好好研究一下,
不然創建好的組件以為隨時可以套用在各種場合~
沒處理好~都花時間在解決CSS衝突啊~
感覺未來JS也可能會衝突 www


分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.