EP36 - ex8. TodoMVC

更新於 發佈於 閱讀時間約 25 分鐘
印象中上過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
raw-image


留言
avatar-img
留言分享你的想法!
avatar-img
卡關的人生
2會員
73內容數
分享生活趣事~
卡關的人生的其他內容
2024/11/10
Vue 提供了多種動畫技術來提升應用程式的互動性,包括基於 CSS 類別的動畫、基於狀態的動畫,以及使用監視器來動畫化數值。基於類別的動畫可通過動態添加 CSS 類別來觸發,像是觸發按鈕搖動效果。基於狀態的動畫則是透過樣式綁定,根據互動動態調整元素的外觀,例如根據滑鼠位置改變背景顏色。
Thumbnail
2024/11/10
Vue 提供了多種動畫技術來提升應用程式的互動性,包括基於 CSS 類別的動畫、基於狀態的動畫,以及使用監視器來動畫化數值。基於類別的動畫可通過動態添加 CSS 類別來觸發,像是觸發按鈕搖動效果。基於狀態的動畫則是透過樣式綁定,根據互動動態調整元素的外觀,例如根據滑鼠位置改變背景顏色。
Thumbnail
2024/11/09
Web Components 是一組網頁原生 API,允許開發者創建可重複使用的自訂元素。Vue 與 Web Components 是互補的技術,Vue 支援整合和創建自訂元素。
Thumbnail
2024/11/09
Web Components 是一組網頁原生 API,允許開發者創建可重複使用的自訂元素。Vue 與 Web Components 是互補的技術,Vue 支援整合和創建自訂元素。
Thumbnail
2024/11/08
Vue 建議使用模板構建應用程式,但在需要 JavaScript 的全程式化功能時,渲染函數可派上用場。渲染函數通過 h() 函數創建 vnode,h 是 hyperscript 的簡寫,能生成 HTML 的 JavaScript。
Thumbnail
2024/11/08
Vue 建議使用模板構建應用程式,但在需要 JavaScript 的全程式化功能時,渲染函數可派上用場。渲染函數通過 h() 函數創建 vnode,h 是 hyperscript 的簡寫,能生成 HTML 的 JavaScript。
Thumbnail
看更多
你可能也想看
Thumbnail
2025 vocus 推出最受矚目的活動之一——《開箱你的美好生活》,我們跟著創作者一起「開箱」各種故事、景點、餐廳、超值好物⋯⋯甚至那些讓人會心一笑的生活小廢物;這次活動不僅送出了許多獎勵,也反映了「內容有價」——創作不只是分享、紀錄,也能用各種不同形式變現、帶來實際收入。
Thumbnail
2025 vocus 推出最受矚目的活動之一——《開箱你的美好生活》,我們跟著創作者一起「開箱」各種故事、景點、餐廳、超值好物⋯⋯甚至那些讓人會心一笑的生活小廢物;這次活動不僅送出了許多獎勵,也反映了「內容有價」——創作不只是分享、紀錄,也能用各種不同形式變現、帶來實際收入。
Thumbnail
如何在 Vite 專案中安裝和設置 TypeScript 及路徑別名的步驟,包括安裝必要的依賴、配置 vite.config.js、tsconfig.json 的設置,及如何創建類型聲明文件來正確識別 .vue 文件。
Thumbnail
如何在 Vite 專案中安裝和設置 TypeScript 及路徑別名的步驟,包括安裝必要的依賴、配置 vite.config.js、tsconfig.json 的設置,及如何創建類型聲明文件來正確識別 .vue 文件。
Thumbnail
先前提到 Quasar 的 Dialog Plugin 很好用,再讓我補充一個用法。
Thumbnail
先前提到 Quasar 的 Dialog Plugin 很好用,再讓我補充一個用法。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
Thumbnail
你好,在下最近在學習開發web,學了html css js,也得出一些心得,由於網路上已有許多教學,所以我會著重在如何開發出to do List,以及解釋我寫的程式碼。相關的教學我會直接貼網址。如果我有什麼地方出錯,或者是可以寫得更好,歡迎在下方留言,討論。 首先先介紹我的開發環境: 我用了vs
Thumbnail
你好,在下最近在學習開發web,學了html css js,也得出一些心得,由於網路上已有許多教學,所以我會著重在如何開發出to do List,以及解釋我寫的程式碼。相關的教學我會直接貼網址。如果我有什麼地方出錯,或者是可以寫得更好,歡迎在下方留言,討論。 首先先介紹我的開發環境: 我用了vs
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News