EP36 - ex8. TodoMVC

更新於 2024/10/13閱讀時間約 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
2會員
71內容數
分享生活趣事~
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
卡關的人生 的其他內容
這段代碼展示了如何使用 Vue 的 <TransitionGroup> 組件來實現帶過渡效果的列表,這是一個實用的例子,對於學習前端動畫效果非常有幫助。
透過 Vue 3,可以輕鬆實現模態對話框。在 App.vue 中,使用按鈕來顯示模態視窗,並利用 Teleport 將其渲染到 body 中。在 Modal.vue 中,透過插槽提供自定義標題、主體和頁腳,並使用 Transition 組件添加顯示和隱藏的過渡效果。
引入 PolyGraph 子組件,並使用 ref 和 reactive 來管理標籤及其對應值。AxisLabel.vue 負責顯示每個統計數據標籤,並根據數據計算其在圖形中位置。PolyGraph.vue 則負責繪製多邊形和圓形,並透過 valueToPoint 函數將統計數據轉換為 SVG 坐標。
Tree View 是一種適合於建立父子組件關係的結構,能夠展示多層級的數據,特別適合遞回渲染。這個組件允許用戶雙擊項目將其轉換為文件夾,並通過 TreeItem 組件遞歸渲染子項目。
在 Vue 3 中使用計算屬性來實現表格的排序和篩選功能。通過創建一個可重用的表格組件(DemoGrid),可以輕鬆管理和顯示外部數據。
這段代碼示範了如何從 GitHub 的 API 抓取最新的 Vue.js 提交數據並顯示在網頁上。這是後端 API 串接的一個範例,展示了如何動態地獲取和顯示數據。
這段代碼展示了如何使用 Vue 的 <TransitionGroup> 組件來實現帶過渡效果的列表,這是一個實用的例子,對於學習前端動畫效果非常有幫助。
透過 Vue 3,可以輕鬆實現模態對話框。在 App.vue 中,使用按鈕來顯示模態視窗,並利用 Teleport 將其渲染到 body 中。在 Modal.vue 中,透過插槽提供自定義標題、主體和頁腳,並使用 Transition 組件添加顯示和隱藏的過渡效果。
引入 PolyGraph 子組件,並使用 ref 和 reactive 來管理標籤及其對應值。AxisLabel.vue 負責顯示每個統計數據標籤,並根據數據計算其在圖形中位置。PolyGraph.vue 則負責繪製多邊形和圓形,並透過 valueToPoint 函數將統計數據轉換為 SVG 坐標。
Tree View 是一種適合於建立父子組件關係的結構,能夠展示多層級的數據,特別適合遞回渲染。這個組件允許用戶雙擊項目將其轉換為文件夾,並通過 TreeItem 組件遞歸渲染子項目。
在 Vue 3 中使用計算屬性來實現表格的排序和篩選功能。通過創建一個可重用的表格組件(DemoGrid),可以輕鬆管理和顯示外部數據。
這段代碼示範了如何從 GitHub 的 API 抓取最新的 Vue.js 提交數據並顯示在網頁上。這是後端 API 串接的一個範例,展示了如何動態地獲取和顯示數據。
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
如何在 Vite 專案中安裝和設置 TypeScript 及路徑別名的步驟,包括安裝必要的依賴、配置 vite.config.js、tsconfig.json 的設置,及如何創建類型聲明文件來正確識別 .vue 文件。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
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
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
如何在 Vite 專案中安裝和設置 TypeScript 及路徑別名的步驟,包括安裝必要的依賴、配置 vite.config.js、tsconfig.json 的設置,及如何創建類型聲明文件來正確識別 .vue 文件。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
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
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。