一、初始化項目
1、下載模板
進入github中https://github.com/tastejs/todomvc-app-template,並且在命令行將其clone下來
git clone https://github.com/tastejs/todomvc-app-template.git
2、安裝依賴
進入項目目錄中安裝依賴
npm install
3、引入vue.js文件
首先在命令行中安裝vue
npm install vue
然后再index.html中引入
然后再app.js文件中寫入相應的邏輯代碼
二、實現功能
1、數據列表渲染功能
1.1 功能分析
- 有數據
每一個數據對象: { id:1, content:'', complated:'' #表示是否已經完成的任務true(false) }
並且每一個數據對象有三個狀態:
未完成(沒有樣式)
已完成(.completed )
編輯中( .editing )
- 無數據
輸入框下面的部分應該隱藏起來
1.2 實現
- 創建一個數據源
items:[ {id:1,content:'dddd',completed:false}, {id:2,content:'aaaa',completed:false}, {id:3,content:'bbbb',completed:false}, {id:4,content:'cccc',completed:false}, ]
- 處理有數據的情況
<ul class="todo-list"> <!-- These are here just to show the structure of the list items --> <!-- List items should get the class `editing` when editing and `completed` when marked as completed --> <!-- 根據不同狀態獲取相應的樣式,三種狀態 --> <li v-for="(item,index) in items" :class="{completed:item.completed}"> <div class="view"> <!-- v-model進行雙向綁定,checkbox是否選中 --> <input class="toggle" type="checkbox" v-model="item.completed"> <!-- 獲取對應對象的內容 --> <label>{{item.content}}</label> <!-- 將id傳入用於刪除對應的數據 --> <button class="destroy" :value="item.id"></button> </div> <input class="edit" value="Create a TodoMVC template"> </li> </ul>
- 處理無數據的情況
利用v-show指令判斷數組長度是否為0,也就是是否為false
<footer class="footer" v-show="items.length"> <!-- This should be `0 items left` by default --> <span class="todo-count"><strong>0</strong> item left</span> <!-- Remove this if you don't implement routing --> <ul class="filters"> <li> <a class="selected" href="#/">All</a> </li> <li> <a href="#/active">Active</a> </li> <li> <a href="#/completed">Completed</a> </li> </ul> <!-- Hidden if no completed items are left ↓ --> <button class="clear-completed">Clear completed</button> </footer>
2、添加任務功能
2.1 功能分析
- 將輸入的內容添加到任務列表中
- 如果輸入為空,不做任何事情
- 按enter鍵添加到任務列表,並清空輸入框
2.2 實現
<header class="header"> <h1>todoapp</h1> <!--綁定鍵盤事件添加數據--> <input @keyup.enter="addItem" class="new-todo" placeholder="What needs to be done?" autofocus> </header>
在app.js文件的methods參數寫入方法
addItem(event){ //獲取文本框中的值 const newValue=event.target.value.trim(); //判斷是否為空,如果為空什么也不做 if(!newValue.length){ return } //如果不為空將新值添加到數組中 newObject={ id:this.items.length+1, //生成一個新的id content:newValue, completed:false }; this.items.push(newObject); //將文本框置空 event.target.value='' }
3、顯示所有未完成任務數功能
3.1 功能分析
- 當數組發生變化時計算內部對象的個數,可通過計算屬性
- 利用filter函數篩選出未完成任務的個數
- 任務數量為單數時為item,為復數時為items
3.2 實現
<!-- 返回所有未完成任務的數量,並且如果為單數就為item,否則為items --> <span class="todo-count"><strong>{{ incomplete }}</strong> item{{ incomplete===1? '' : 's' }} left</span>
在app.js文件的computed參數中寫入對應的方法
incomplete(){ //箭頭函數返回未完成任務的個數 return this.items.filter(item=>!item.completed).length // this.items.filter(function (item) { // return !item.completed // }).length }
4、切換所有任務狀態
4.1 功能分析
- 點擊輸入框前面的復選框后,將所有任務標記為與復選框相同的狀態
(1)使用計算屬性中的set方法,此時需要v-model進行數據的雙向綁定,通過監聽數據屬性,獲取新的checkbox的值
(2)將獲取的值賦給每一個任務項
- 當 選中/取消 某個任務后,復選框 也應同步更新狀態
(1)使用計算屬性的get方法,判斷所有incomplete是否為 0 ,
(2)綁定了 incomplete,當 incomplete發生變化后, 自動更新復選框狀態(如果為0說明任務已經全部完成,復選框會自動選中,反之不選中
4.2 實現
index.html
<input id="toggle-all" v-model="isSelectAll" class="toggle-all" type="checkbox">
app.js
isSelectAll:{ //循環數據源中的每一個對象,並且將通過v-model雙向綁定獲取的值賦給每一個item中的狀態,從而根據input checkbox的狀態去頂任務的狀態 set:function (newState) { this.items.forEach(function (item) { item.completed=newState; }) }, //根據任務完成的狀態完成綁定v-model的input checkbox框的狀態獲取 get:function () { return this.incomplete===0 } }
5、刪除任務項
5.1 功能分析
- 懸停在某個任務項上顯示 X 移除按鈕,可點擊移除當前任務項
(1) 移除按鈕處添加點擊事件
(2)通過數組函數 splice 移除任務
5.2 實現
index.html
<button class="destroy" :value="item.id" @click="removeItem(index)"></button>
app.js
//移除對象 splice(),傳入移除對象的索引,以及從此處開始完后刪掉的數量 removeItem(index){ this.items.splice(index,1) },
6、編輯任務項
6.1 功能分析
- 雙擊 <label> (某個任務項)進入編輯狀態(在 <li> 上通過 .editing 進行切換狀態)
- 進入編輯狀態后輸入框顯示原內容,並會自動獲取編輯焦點
- 輸入狀態按 Esc 取消編輯,editing 樣式應該被移除
- 按 Enter 鍵 或 失去焦點時 保存改變數據,移除 editing 樣式
6.2 實現
- 雙擊 <label> (某個任務項)進入編輯狀態(在 <li> 上通過 .editing 進行切換狀態)
<!-- 獲取對應對象的內容,在內容的標簽上綁定雙擊事件-->
<label @dblclick="toEdit(item)">{{item.content}}</label>
//雙擊進入編輯模式,也就是加入.editing樣式 toEdit(item){ this.currentItem=item },
- 進入編輯狀態后輸入框顯示原內容,並會自動獲取編輯焦點
<!-- 顯示點擊編輯后的默認值:value="item.content" --> <input class="edit" v-todo-focus="item===currentItem" :value="item.content" >
//自定義局部指令,用於聚焦編輯框修改內容,當進入編輯模式的對象與傳入的對象是同一個時聚焦,防止聚焦到別處 directives:{ "todo-focus":{ //當指令的值更新后會調用此方法 update(el,binding){ //el表示作用的元素 //binding表示指令后輸入的內容 if(binding.value){ el.focus() } } } },
- 輸入狀態按 Esc 取消編輯,editing 樣式應該被移除
<!-- 顯示點擊編輯后的默認值:value="item.content" --> <input class="edit" @keyup.esc="cancelEdit" v-todo-focus="item===currentItem" :value="item.content" >
//點擊鍵盤的esc取消編輯 cancelEdit(){ this.currentItem=null },
- 按 Enter 鍵 或 失去焦點時 保存改變數據,移除 editing 樣式
<!-- 顯示點擊編輯后的默認值:value="item.content" --> <input class="edit" @keyup.esc="cancelEdit" @keyup.enter="saveData(item,index,$event)" @blur="saveData(item,index,$event)" v-todo-focus="item===currentItem" :value="item.content" >
//通過enter以及blur事件,保存數據,只有當獲取焦點才會觸發該事件 saveData(item,index,event){ //獲取對應文本框中去除空格后的內容 const content=event.target.value.trim(); //判斷內容是否為空,如果為空,刪除任務項 if(!content){ //重用removeItem函數刪除 this.removeItem(index) } //否則對數據進行更新 item.content=content; //更新后移除編輯樣式,.editing this.currentItem=null },
7、清除所有任務項
7.1 功能分析
- 單擊右下角 Clear completed 按鈕時,移除所有已完成任務
- 當列表中沒有已完成的任務時,應該隱藏 Clear completed 按鈕
7.2 實現
- 單擊右下角 Clear completed 按鈕時,移除所有已完成任務
在index.html添加點擊事件,然后再app.js中通過filter函數鍋爐出所有未完成任務,並且賦給items
index.html
<!-- Hidden if no completed items are left ↓ --> <!--在對應的地方添加點擊事件--> <button @click="removeAllCompleted" class="clear-completed">Clear completed</button>
app.js
//過濾出所有未完成的任務項,並且將過濾后的數據賦值給items removeAllCompleted(){ this.items= this.items.filter((item)=>!item.completed) },
- 當列表中沒有已完成的任務時,應該隱藏 Clear completed 按鈕
判斷總的任務數與沒有完成任務數的大小,如果當總任務數 ( items.length ) > 未完成數 ( incomplete) ,說明列表中還有已完成數據,則是顯示按鈕;反之不顯示。
index.html
<button @click="removeAllCompleted" class="clear-completed" v-show="items.length > incomplete">Clear completed</button>
app.js
//計算屬性 incomplete(){ //箭頭函數返回未完成任務的個數 return this.items.filter(item=>!item.completed).length // this.items.filter(function (item) { // return !item.completed // }).length },
8、 過濾出不同狀態 的數據
8.1 功能分析
- 根據點擊的狀態不同,獲取不同狀態下的數據
- 改變不同狀態下的樣式
8.2 實現
- 根據點擊的狀態不同,獲取不同狀態下的數據
(1)在 data 中定義接收狀態變化的值filterStatus
(2)通過 window.onhashchange 獲取點擊的路由 hash (# 開頭的),來獲取對應的那個狀態值,並將狀態值賦值給 filterStatus
(3)定義一個計算屬性 filterItems 用於過濾出目標數據, 用於感知 filterStatus 的狀態值變化,當變化后,通過 switch-case + filter 過濾出目標數據。
app.js
//1、定義變量 data:{ filterState:'all', }, //2、獲取路由hash值,並且截取需要的路由,當截取的為空時返回‘all’ window.onhashchange=function () { // window.location.hash 獲取的是這樣的數據 #/active const hash=window.location.hash.substr(2) || 'all'; //將狀態值賦值給vm實例中的filterState vm.filterState = hash }; //第一次訪問生效,手動調用一次 window.onhashchange()
//3、定義計算屬性filterItems //過濾出不同狀態下的數據,以this.filterState為過濾條件 filterItems(){ switch (this.filterState) { case "active": return this.items.filter(item=>!item.completed); break case "completed": return this.items.filter(item=>item.completed); break default: return this.items; break } },
index.html
<!--將v-for循環的items替換為filterItems--> <li v-for="(item,index) in filterItems" :class="{completed:item.completed,editing:item===currentItem}">
- 改變不同狀態下的樣式
<ul class="filters"> <li> <a class="selected" href="#/">All</a> </li> <li> <a href="#/active">Active</a> </li> <li> <a href="#/completed">Completed</a> </li> </ul>
將上述被選中的樣式切換為:
class="selected"
如下:
<ul class="filters"> <li> <a :class="{selected:filterState === 'all'}" href="#/">All</a> </li> <li> <a :class="{selected:filterState === 'active'}" href="#/active">Active</a> </li> <li> <a :class="{selected:filterState === 'completed'}" href="#/completed">Completed</a> </li> </ul>
9、數據持久化(localStorage)
9.1 功能分析
目前數據只是單純的放在內存中,自己定義的數組:
data:{ items:[ {id:1,content:'dddd',completed:false}, {id:2,content:'aaaa',completed:false}, {id:3,content:'bbbb',completed:false}, {id:4,content:'cccc',completed:false}, ], currentItem:null, filterState:'all', },
如果需要保存在本地,可以使用localStorage ,它主要是用於本地存儲數據。語法如下:
//保存數據語法: localStorage.setItem("key", "value"); //讀取數據語法: var data= localStorage.getItem("key"); //刪除數據語法: localStorage.removeItem("key");
本項目中使用的步驟如下:
- 定義 itemStorage 數據存儲對象,內部自定義 fetch 獲取本地數據 ,save 存數據到本地。
- 修改 Vue 實例中 data 選項的 items 屬性,通過 itemStorage.fetch() 方法初始化數據
- Vue 實例中增加一個 watch 選項,用於監聽 items 的變化,一旦變化通過 itemStorage.save() 重新保存數據到本地
9.2 實現
- 定義 itemStorage 數據存儲對象,里面自定義 fetch 獲取本地數據 ,save 存數據到本地。
var STOREGE_KEY = "todo-items"; //定義localstorege對象,注意是在Vue實例外面定義的 const itemStorage = { //獲取本地數據的方法 fetch:function () { //獲取數據並且數據反序列化,變成數組對象,如果為空,則是空數組 return JSON.parse(localStorage.getItem(STOREGE_KEY) || '[]') }, //保存數據到本地,items就是需要保存的數據源,並且以JSON字符串的格式存儲 save:function (items) { localStorage.setItem(STOREGE_KEY,JSON.stringify(items)) } };
- 修改 Vue 實例中 data 選項的 items 屬性,通過 itemStorage.fetch() 方法初始化數據
var vm = new Vue({ el:'#todoapp', data:{ // items:[ // {id:1,content:'dddd',completed:false}, // {id:2,content:'aaaa',completed:false}, // {id:3,content:'bbbb',completed:false}, // {id:4,content:'cccc',completed:false}, // ], //從本地獲取數據 items:itemStorage.fetch(), currentItem:null, filterState:'all', },
- Vue 實例中增加一個 watch 選項,用於監聽 items 的變化,一旦變化通過 itemStorage.save() 重新保存數據到本地
//監聽器,用於本地化數據的存儲,一旦數組對象有變化,立即存儲 watch:{ //監聽items,一旦items發生變化就會執行 items:{ deep:true,//需要監聽數組對象內部的變化,需要指定deep:true handler(newitems,olditems){ // newitems:新的數組對象 // olditems:之前的數組對象 itemStorage.save(newitems) } } },
項目地址:https://github.com/ShenJianPing0307/todo-demo