我的 github
項(xiàng)目地址:https://github.com/Nicklaus6/todomvc-vue
如果對(duì)你有幫助希望可以點(diǎn)個(gè) star
哦 ~
一扑毡、項(xiàng)目初始化
1.下載模板
在存放該項(xiàng)目的目錄下執(zhí)行:
git clone https://github.com/tastejs/todomvc-app-template.git
2. 安裝依賴
進(jìn)入項(xiàng)目目錄
cd todomvc-vue
在項(xiàng)目目錄下安裝依賴
yarn
3. 引入vue
安裝vue
yarn add vue
在 index.html
中引入 vue
<script src="node_modules/vue/dist/vue.js"></script>
在 app.js
中創(chuàng)建vue對(duì)象
(function (Vue) {
window.app = new Vue({
el:"#todoapp",
})
})(Vue)
并在index.html
中將其掛載到 DOM 元素 (#todoapp
)
<section class="todoapp" id="todoapp">...</section>
完成以上操作后,用瀏覽器打開(kāi) index.html
婚温,若界面是以下這樣就說(shuō)明項(xiàng)目 初始化成功 了。
二揖膜、功能實(shí)現(xiàn)和思考
1. 列表數(shù)據(jù)渲染
- 創(chuàng)建數(shù)據(jù)并加入Vue實(shí)例中的
data
對(duì)象
let todos = [
// 先寫(xiě)兩條假數(shù)據(jù)測(cè)試一下
{ id: 1, content: "阿巴阿巴", completed: true },
{ id: 2, content: "馬卡馬卡", completed: false }
]
window.app = new Vue({
data () {
return {
todos: todos
}
},
})
-
無(wú)數(shù)據(jù)時(shí)
.main
和.footer
隱藏 :v-if
條件渲染<section class="main" v-if="todos.length">...</section> <section class="footer" v-if="todos.length">...</section>
思考:為什么這里使用 v-if
而不是 v-show
呢吼肥?他們的區(qū)別是跷敬?
共同點(diǎn) : 他們的功能都是 條件渲染 。
不同點(diǎn) : v-show
的原理是修改 css
的display屬性柒爸,并沒(méi)有操作 dom
元素准浴。
? v-if
的原理是根據(jù)條件,動(dòng)態(tài)地銷(xiāo)毀或添加 dom
元素捎稚。但 v-if
也是 惰性
? 的乐横,如果初始條件為 false
,則什么都不做今野,等到條件為 true
了再開(kāi)始渲染葡公。
因此,v-if
有更高的切換開(kāi)銷(xiāo)条霜,而 v-show
有更高的初始渲染開(kāi)銷(xiāo)催什。如果需要頻繁切換,建議使用 v-show
宰睡,如果不需要頻繁切換蒲凶,則可以使用 v-if
。而這里.main
和 .footer
并不會(huì)頻繁切換狀態(tài)拆内,所以使用 v-if
豹爹。
?
-
有數(shù)據(jù)時(shí)
動(dòng)態(tài)渲染數(shù)據(jù)列表 :
v-for
列表渲染綁定相應(yīng)狀態(tài)下的 class :
:class
class 的綁定checkbox 選中狀態(tài)切換 :
v-model
雙向數(shù)據(jù)綁定label 內(nèi)容渲染 :
Mustache
語(yǔ)法
<ul class="todo-list"> <li v-for="(item,index) in todos" :key="item.id" :class="{completed:item.completed}"> <div class="view"> <input class="toggle" type="checkbox" v-model="item.completed"> <label>{{ item.content }}</label> <button class="destroy"></button> </div> <input class="edit" value="Create a TodoMVC template"> </li> </ul>
思考 :
-
為什么在使用
v-for
時(shí) 還要使用:key
?Vue在更新使用
v-for
渲染的列表元素時(shí)矛纹,會(huì)采用一種 就地復(fù)用 策略臂聋,盡可能的嘗試就地修改/復(fù)用相同類(lèi)型元素。而:key
給了每個(gè)節(jié)點(diǎn)一個(gè) 唯一標(biāo)識(shí),讓虛擬 DOM 中的 Diff 算法正確識(shí)別節(jié)點(diǎn)孩等,從而重用和重新排序現(xiàn)有元素艾君,從而更加 高效地更新虛擬 DOM。
-
v-model
的原理肄方?v-model
用于表單數(shù)據(jù)的雙向綁定冰垄,本質(zhì)上是語(yǔ)法糖。它背后做了兩個(gè)操作 :
v-bind
綁定一個(gè) value 屬性权她,v-on
給當(dāng)前元素綁定 input 事件虹茶。<input v-model="something"></input> 以上操作等價(jià)于 <input :value="something" @input="something = $event.target.value"></input> 先綁定一個(gè) something 屬性,在通過(guò)監(jiān)聽(tīng) input 事件隅要,當(dāng)用戶改變輸入框數(shù)據(jù)時(shí)蝴罪,通過(guò)設(shè)置當(dāng)前事件的目標(biāo)dom的value,從而實(shí)現(xiàn)雙向數(shù)據(jù)綁定的效果步清。
2. 添加新的 todo
-
按下回車(chē)要门,輸入內(nèi)容不為空,添加一條
todo
:-
@keyup.enter
監(jiān)聽(tīng)鍵盤(pán)回車(chē)事件并在 vue 的methods
中添加相應(yīng)方法 - 內(nèi)容為空則什么都不做
- 添加完后輸入框內(nèi)容清空
<input class="new-todo" placeholder="What needs to be done?" autofocus @keyup.enter="addTodo"> </input>
methods: { addTodo ($event) { // 創(chuàng)建 newTodo 對(duì)象廓啊,獲取數(shù)據(jù) const newTodo = { id: this.todos.length + 1, content: $event.target.value.trim(), completed: false } // 如果內(nèi)容為空欢搜,什么都不做 if (!newTodo.content.length) return // 如果內(nèi)容不為空,將 newTodo 加入 todos 中 this.todos.push(newTodo) // 清空輸入框內(nèi)容 $event.target.value = '' } }
-
3. 刪除 todo
-
點(diǎn)擊
.destroy
谴轮,刪除所在的todo
:@click
監(jiān)聽(tīng)按鈕點(diǎn)擊事件并在 vue 的methods
中添加相應(yīng)方法-
數(shù)組的
splice
方法刪除todo
<button class="destroy" @click="destroyTodo(index)"></button>
methods: { destroyTodo (index) { // 用 splice 方法通過(guò)參數(shù) index 來(lái)找到要?jiǎng)h除的 todo炒瘟,刪除一項(xiàng) this.todos.splice(index, 1) } }
?
4. 編輯 todo
-
雙擊
label
,進(jìn)入編輯模式@dblclick
監(jiān)聽(tīng)label
雙擊事件:class
給所在的li
綁定 class.editing
這里設(shè)置一個(gè)中間變量
currentEditing
(就像一種狀態(tài))第步,當(dāng)監(jiān)聽(tīng)到label
雙擊事件時(shí)唧领,currentEditing = item
,而當(dāng)item === currentEditing
時(shí)雌续,就給所在的li
綁定 class<li v-for="(item,index) in todos" :key="item.id" :class="{ completed: item.completed , editing: item === currentEditing }"> </li>
<label @dblclick="currentEditing = item">{{ item.content }}</label>
-
局部自定義指令
directives
讓輸入框自動(dòng)獲取焦點(diǎn)<input class="edit" :value="item.content" v-editing-focus="item === currentEditing"> </input>
directives:{ // update 在所有組件的 VNode(虛擬節(jié)點(diǎn)) 更新時(shí)調(diào)用斩个,但可能發(fā)生在其子 VNode 更新之前。 update(el,binding){ // el : 用來(lái)操作元素 DOM // binding.value : 指令的綁定值 這里即 item === currentEditing if(binding.value){ el.focus() } } }
?
- 輸入內(nèi)容后回車(chē)或失焦驯杜,將原本的
todo
內(nèi)容替換為輸入的內(nèi)容-
@keyup.enter
監(jiān)聽(tīng)鍵盤(pán)回車(chē)事件受啥;@blur
監(jiān)聽(tīng)失焦事件<input class="edit" :value="item.content" v-editing-focus="item === currentEditing" @keyup.enter="saveEditing(item,index,$event)" @blur="saveEditing(item,index,$event)">
-
-
在vue 的
methods
中添加相應(yīng)方法saveEditing (item, index, $event) { // 將輸入的內(nèi)容保存到 newContent 變量中 const newContent = $event.target.value.trim() // 如果內(nèi)容為空 就刪除 todo if (!newContent) this.destroyTodo(index) //將原本容替換為輸入的內(nèi)容 item.content = newContent //通過(guò)設(shè)置 currentEditing ,移除掉 .editing 鸽心,退出編輯模式滚局。 this.currentEditing = null }
-
:value
給input
綁定內(nèi)容<input class="toggle" type="checkbox" v-model="item.completed" :value="item.content">
- 按下 esc,退出編輯模式
-
@keyup.esc
監(jiān)聽(tīng)鍵盤(pán)回車(chē)事件<input class="edit" :value="item.content" v-editing-focus="item === currentEditing" @keyup.enter="saveEditing(item,index,$event)" @blur="saveEditing(item,index,$event)" @keyup.esc="quitEditing">
-
并在vue 的
methods
中添加相應(yīng)方法quitEditing () { // 通過(guò)設(shè)置 currentEditing 移除掉 .editing 退出編輯模式 this.currentEditing = null }
5. 標(biāo)記所有任務(wù)完成或者未完成
-
點(diǎn)擊
.toggle-all
顽频,將所有的todos
的完成狀態(tài) 和.toggleAll
的勾選狀態(tài) 綁定-
@click
監(jiān)聽(tīng)按鈕點(diǎn)擊事件<input id="toggle-all" class="toggle-all" type="checkbox" @click="toggleAll">
-
并在 vue 的
methods
中添加相應(yīng)方法methods: { toggleAll ($event) { // 獲取 .toggleAll 的勾選狀態(tài) let isToggled = $event.target.checked // 將所有的 todos 的完成狀態(tài) 和 .toggleAll 的勾選狀態(tài) 綁定 this.todos.forEach(item => item.completed = isToggled); }, }
-
-
將
.toggleAll
的勾選狀態(tài) 和todos
是否全選綁定-
給
.toggle-all
的checked
屬性綁定一個(gè)的計(jì)算屬性來(lái)監(jiān)聽(tīng)單選框選中情況的改變<input id="toggle-all" class="toggle-all" type="checkbox" @click="toggleAll" :checked="isAllChecked"> <!-- 也可以寫(xiě) v-model="isAllChecked" 因?yàn)?checkbox 使用 checked property 和 change 事件 -->
-
并在 vue 的
computed
中添加計(jì)算屬性computed: { isAllChecked () { return !this.todos.find(item => !item.completed) } },
-
思考:
-
computed
vsmethods
?computed
vswatch
?computed
vsmethods
:計(jì)算屬性是基于它們的響應(yīng)式依賴進(jìn)行緩存的藤肢。只有相關(guān)響應(yīng)式依賴發(fā)生改變時(shí),他們才會(huì)重新求值糯景。而方法會(huì)在每次重新渲染時(shí)調(diào)用函數(shù)嘁圈,不占用緩存但是會(huì)消耗一定時(shí)間省骂。
computed
vswatch
:雖然計(jì)算屬性在大多數(shù)情況下更合適,但是當(dāng)需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開(kāi)銷(xiāo)較大的操作時(shí)最住,使用偵聽(tīng)器更合適钞澳。
-
v-model
在內(nèi)部為 checkbox 使用的 property`和拋出的事件?checkbox 和 radio 使用
checked
property 和change
事件涨缚。什么是change
事件轧粟?其實(shí)就是 HTML 的onchange
事件。它是元素值被改變(用戶改變脓魏,用代碼內(nèi)部改變無(wú)效)且表單失焦時(shí)觸發(fā)的事件兰吟。所以此時(shí)
v-model
的原理是:<input type="checkbox" v-model="something"></input> 以上操作等價(jià)于 <input type="checkbox" :checked="something" @change="something = $event.target.checked"> </input>
同理,select 字段將
value
作為 prop 并將change
作為事件茂翔。<select name="" id="" v-model="something"> <option disabled value="">請(qǐng)選擇</option> <option>A</option> <option>B</option> </select> 以上操作等價(jià)于 <select name="" id="" :value="something" @change="something = $event.target.value"> <option disabled value="">請(qǐng)選擇</option> <option>A</option> <option>B</option> </select>
6. 計(jì)數(shù)
-
在
.todo-count
顯示未完成的todo
的數(shù)量-
模板語(yǔ)法
<span class="todo-count"><strong>{{todoCount}}</strong> item left</span>
-
-
在 vue 中添加計(jì)算屬性
computed: { todoCount () { // es6 的 filter 方法 return this.todos.filter(item => !item.completed).length } },
7. 清除所有完成項(xiàng)
-
點(diǎn)擊
clear-completed
混蔼,清除所有完成項(xiàng)-
@click
監(jiān)聽(tīng)按鈕點(diǎn)擊事件并在 vue 中添加相應(yīng)方法<button class="clear-completed" @click="clearCompleted">Clear completed</button>
methods: { clearCompleted () { this.todos = this.todos.filter(item => !item.completed) } },
-
-
當(dāng)至少有一項(xiàng)完成項(xiàng)才顯示
-
v-show
切換狀態(tài)<button class="clear-completed" @click="clearCompleted" v-show="hasCompleted">Clear completed</button>
-
-
綁定計(jì)算屬性判斷是否至少有一項(xiàng)完成項(xiàng)
computed: { hasCompleted () { // 當(dāng)至少有一項(xiàng)完成項(xiàng)才顯示 return this.todos.filter(item => item.completed > 0).length > 0 } },
8. 三種狀態(tài)數(shù)據(jù)過(guò)濾
-
點(diǎn)擊不同的狀態(tài),獲取相應(yīng)的數(shù)據(jù)
-
在 vue 的
data
中保存過(guò)濾狀態(tài)檩电,默認(rèn)為 'all'data () { return { todos: todos, currentEditing: null, filterState: 'all' } },
-
-
通過(guò)
window.onhashchange
獲取點(diǎn)擊狀態(tài)的路由 hash 保存為當(dāng)前的狀態(tài)值,并且賦給 data 中的過(guò)濾狀態(tài)// 路由狀態(tài)切換 // 當(dāng) 一個(gè)窗口的 hash (URL 中 # 后面的部分)改變時(shí)就會(huì)觸發(fā) hashchange 事件 window.onhashchange = function () { // 獲取當(dāng)前點(diǎn)擊狀態(tài)的路由 hash 獲取的 location.hash 是 #/all 這樣的數(shù)據(jù) const hash = window.location.hash.substr(2) || 'all' // 將路由狀態(tài)賦給 過(guò)濾狀態(tài) window.app.filterState = hash } // 頁(yè)面第一次進(jìn)來(lái)保持狀態(tài) window.onhashchange()
-
通過(guò)計(jì)算屬性來(lái)渲染過(guò)濾狀態(tài)下的渲染的數(shù)據(jù)
定義計(jì)算屬性:根據(jù)過(guò)濾狀態(tài)返回相應(yīng)的
todo
computed: { filterTodos () { switch (this.filterState) { case 'active': return this.todos.filter(item => !item.completed); break case 'completed': return this.todos.filter(item => item.completed); break default: return this.todos; break } } },
修改 todo 的列表循環(huán)
<li v-for="(item,index) in filterTodos" :key="item.id" :class="{ completed: item.completed, editing: item === currentEditing }"> </li>
-
根據(jù)狀態(tài)改變狀態(tài)按鈕的樣式
-
:class
給選中的狀態(tài)綁定.selected
樣式<ul class="filters"> <li> <a :class="{selected:filterState==='all'}" href="#/">All</a> </li> <li> <a href="#/active" :class="{selected:filterState==='active'}">Active</a> </li> <li> <a href="#/completed" :class="{selected:filterState==='completed'}">Completed</a> </li> </ul>
-
9. 數(shù)據(jù)持久化
-
在 vue 實(shí)例外部 定義一個(gè)數(shù)據(jù)存儲(chǔ)對(duì)象, 有以下兩個(gè)方法:
- 獲取本地?cái)?shù)據(jù)
- 保存數(shù)據(jù)到本地
let STOREAGE_KEY = "todo-items" // 定義數(shù)據(jù)存儲(chǔ)對(duì)象 const todoStorage = { // 獲取本地?cái)?shù)據(jù) localStorage.getItem("key") fetch: function () { // 返回獲取的本地?cái)?shù)據(jù)的數(shù)組對(duì)象 ,如果為空府树,則是空數(shù)組 || '[]', return JSON.parse(localStorage.getItem(STOREAGE_KEY) || '[]') }, // 保存數(shù)據(jù)到本地 localStorage.setItem("key","value") save: function (todos) { // 以 JSON 字符串形式存儲(chǔ) todos 數(shù)據(jù) localStorage.setItem(STOREAGE_KEY, JSON.stringify(todos)) } }
-
修改 vue 實(shí)例的
data
中的todos
俐末,以本地?cái)?shù)據(jù)初始化data () { return { todos: todoStorage.fetch(), currentEditing: null, filterState: 'all' }
-
通過(guò) vue 的
watch
監(jiān)聽(tīng)todos
的變化,一有改變就將數(shù)據(jù)保存到本地watch: { // 監(jiān)聽(tīng) todos 變化 todos: { deep: true, // 監(jiān)聽(tīng)對(duì)象內(nèi)部值的變化 handler (newTodos) { todoStorage.save(newTodos) } } },
思考: 為什么使用 localStorage
而不是 sessionStorage
奄侠?
localStorage
用于 長(zhǎng)久 保存整個(gè)網(wǎng)站的數(shù)據(jù)卓箫,關(guān)閉標(biāo)簽頁(yè)數(shù)據(jù)也不會(huì)消失,保存的數(shù)據(jù)沒(méi)有過(guò)期時(shí)間垄潮,直到手動(dòng)刪除烹卒。
localStorage
的語(yǔ)法:
-
保存數(shù)據(jù)
localStorage.setItem("key","value")
-
讀取數(shù)據(jù)
let myLocalStorage = localStorage.getItem("key")
-
刪除數(shù)據(jù)
localStorage.removeItem("key")
sessionStorage
用于只想將數(shù)據(jù)保存在 當(dāng)前會(huì)話 中時(shí),關(guān)閉標(biāo)簽頁(yè)數(shù)據(jù)會(huì)被刪除弯洗。