本項(xiàng)目采用vue+vuex+localStorage共屈,實(shí)現(xiàn)一個(gè)本地存儲(chǔ)的筆記app执隧,項(xiàng)目預(yù)覽見(jiàn)demo,源碼可訪問(wèn)我的github
項(xiàng)目的下載安裝步驟
git clone https://github.com/Anticlimax/vue-note.git
cd vue-note
npm install
npm run dev
讓我們通過(guò)這樣一個(gè)有一定數(shù)據(jù)操作要求的應(yīng)用夹纫,來(lái)學(xué)習(xí)vue和vuex的使用寸宵。
- 起步
首先恤煞,通過(guò)vue-cli構(gòu)建基本應(yīng)用模板,方便開(kāi)發(fā)
npm i vue-cli -g
vue init webpack vue-note
cd vue-note
npm run dev
這樣,我們就起了一個(gè)本地的開(kāi)發(fā)服務(wù)器刊苍,而且可以實(shí)現(xiàn)熱加載,設(shè)置一下編輯器的自動(dòng)保存就能實(shí)時(shí)看到代碼變化濒析。
2.項(xiàng)目邏輯
我們開(kāi)發(fā)的項(xiàng)目的ui如下
![OHLNC51QG}K3D%7VV37Z]L1.png](http://upload-images.jianshu.io/upload_images/3599810-054054226e79bf05.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
大概分為左側(cè)工具欄正什,中間筆記列表及右側(cè)編輯欄,所以我們先構(gòu)建項(xiàng)目的基本骨架号杏。
//App.vue
<template>
<div id="app">
<Toolbar></Toolbar>
<NoteList></NoteList>
<Editor></Editor>
</div>
</template>
<script>
import Toolbar from './components/Toolbar.vue'
import Editor from './components/Editor.vue'
import NoteList from './components/NoteList.vue'
import './assets/css/reset.css'
export default {
name: 'app',
store, //向根組件注入store埠忘,這樣所有的子組件都能夠調(diào)用store
components: {
Toolbar,
NoteList,
Editor
}
}
</script>
這是我們的根實(shí)例,引用了三個(gè)組件Toolbar馒索,NoteList和Editor莹妒,這樣我們只需要分別對(duì)三個(gè)組件進(jìn)行開(kāi)發(fā)就好了。
但是在此之前绰上,我們先思考一下項(xiàng)目的邏輯旨怠,我們要完成的功能如下:
- 通過(guò)Toolbar的添加,收藏和刪除按鈕對(duì)NoteList中的列表項(xiàng)進(jìn)行操作
- NoteList上的All和Favorites按鈕可以切換顯示所有筆記和收藏筆記
- Ediotr上部的title區(qū)和NoteList部分筆記顯示的題目相同
- Ediotr下部的內(nèi)容區(qū)在切換筆記后不會(huì)消失
基于這樣的功能蜈块,我們先來(lái)編寫(xiě)store.js鉴腻,在src文件夾下新建一個(gè)vuex目錄,下包含一個(gè)store.js文件
//store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
let state = {
notes: [],
activeNote: {}
}
const mutations = {
}
export default new Vuex.Store({
state,
mutations
})
這里就是我們的數(shù)據(jù)中心百揭,所有的數(shù)據(jù)操作都由此流入和流出爽哎,接下來(lái)就是寫(xiě)mutations的相關(guān)邏輯,我們先來(lái)完成一個(gè)添加操作作為例子
// store.js
const mutations = {
ADD_NOTE(state){
const newNote = {
title: 'New Note',
favorite: false,
content: '',
id: new Date()
}
state.notes.push(newNote)
state.activeNote = newNote
}
}
這段代碼表示器一,每次添加筆記時(shí)都會(huì)向state下的notes數(shù)組push一個(gè)對(duì)象课锌,并且把激活筆記設(shè)置為新添加的這個(gè)筆記。
接下來(lái)是Toolbar的相關(guān)邏輯
//Toolbar.vue
<template>
<div id="Toolbar">
<ul class="btnList">
<li @click="addNote" ><i class="iconfont icon-jiahao"></i></li>
<li><i class="iconfont icon-wujiaoxingshixin"></i></li>
<li><i class="iconfont icon-cheng"></i></li>
</ul>
</div>
</template>
<script>
export default {
methods:{
addNote(){
this.$store.commit('ADD_NOTE')
}
}
</script>
我們?yōu)樘砑影粹o綁定了一個(gè)addNote事件祈秕,在每次點(diǎn)擊的時(shí)候觸發(fā)渺贤,這個(gè)事件會(huì)向store提交一個(gè)觸發(fā)ADD_NOTE這個(gè)mutation的請(qǐng)求,這樣就向state下的notes新添加了一個(gè)對(duì)象请毛。接下來(lái)我們需要讓這個(gè)筆記顯示出來(lái)志鞍。
//NoteList.vue
<template>
<div id="NoteList">
<div class="header-wrapper">
<h3 class="header">NOTES</h3>
<div class="btn-wrapper">
<a href="#">All Notes</a>
<a href="#">Favorites</a>
</div>
</div>
<ul class="list">
<li v-for="note in noteList">
{{ note.title }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'NodeList',
computed: {
noteList(){
return this.$store.state.notes
}
}
</script>
我們?cè)谶@里先使用計(jì)算屬性取得state下的notes數(shù)組,再對(duì)這個(gè)數(shù)據(jù)進(jìn)行遍歷方仿,就能顯示所有新建的筆記固棚。接下來(lái)來(lái)看Editor中對(duì)標(biāo)題和內(nèi)容的處理
//Editor.vue
<template>
<div id="Editor">
<input type="text" id="EditorTitle" placeholder="請(qǐng)輸入筆記標(biāo)題"
:value="activeNoteTitle" @input="updateTitle">
<textarea name="editor" id="editorPanel"
@input="updateContent" :value="activeNoteText">
</textarea>
</div>
</template>
<script>
export default {
name:'Editor',
computed:{
activeNoteText(){
return this.$store.state.activeNote.content
},
activeNoteTitle(){
return this.$store.state.activeNote.title
}
},
methods:{
updateContent(e){
console.log(this.activeNoteText)
this.$store.commit("EDIT_NOTE", e.target.value)
},
updateTitle(e){
this.$store.commit("EDIT_TITLE",e.target.value)
}
}
}
我們分別對(duì)兩塊輸入?yún)^(qū)域都綁定了input事件,在每次輸入的時(shí)候都會(huì)觸發(fā)相應(yīng)事件仙蚜,讓mutation進(jìn)行響應(yīng)的操作此洲。重點(diǎn)是使用了:value而不是v-model進(jìn)行輸入框數(shù)據(jù)的操作,這是由于vuex本身的設(shè)計(jì)不建議使用雙向綁定鳍征,在嚴(yán)格模式下使用v-model會(huì)報(bào)錯(cuò)黍翎。
依據(jù)以上的例子面徽,大致就可以完成整個(gè)app了艳丛,其余的只是一些邏輯的處理匣掸,接下來(lái)我們來(lái)完成本地存儲(chǔ)的功能。我們希望每次刷新頁(yè)面數(shù)據(jù)都能保存到本地而不是全部清空氮双,這樣就要用到localStorage碰酝,它有如下的基本用法
window.localStorage.setItem('name',data) //保存數(shù)據(jù),放在name鍵下
window.localStorage.getItem('name') // 取出name鍵的數(shù)據(jù)
window.localStorage.removeItem('name') //刪除name字段的數(shù)據(jù)
接下來(lái)需要思考的是戴差,我們改在什么時(shí)候存取數(shù)據(jù)呢送爸?每次數(shù)據(jù)更新都保存顯得有點(diǎn)傻,我決定在頁(yè)面刷新之前來(lái)保存數(shù)據(jù)暖释,在頁(yè)面創(chuàng)建后就獲取數(shù)據(jù)袭厂。所以進(jìn)行如下處理
//Appd.vue
<script>
...
created(){
window.onbeforeunload = ()=>{
this.$store.commit('SAVE_DATA')
}
this.$store.commit('INIT_DATA')
}
...
</script>
//store.js
...
const mutations = {
...
INIT_DATA(state){
if (window.localStorage.getItem('vue-note')) {
this.state = window.localStorage.getItem('vue-note')
}
},
SAVE_DATA(state){
window.localStorage.setItem('vue-note', JSON.stringify(state))
}
}
...
這樣就實(shí)現(xiàn)了在頁(yè)面刷新前保存數(shù)據(jù),在頁(yè)面載入后讀取數(shù)據(jù)球匕,看起來(lái)很美好纹磺,但是實(shí)際操作后發(fā)現(xiàn)問(wèn)題。
雖然我們重寫(xiě)了state為保存的數(shù)據(jù)亮曹,但是視圖并沒(méi)有更新橄杨,這是為什么?四處尋找后終于找到了答案照卦,就在vue的文檔中
深入響應(yīng)式原理
簡(jiǎn)單來(lái)講就是式矫,vue不能檢測(cè)到對(duì)象屬性的添加與刪除,也不允許在已經(jīng)創(chuàng)建的實(shí)例上動(dòng)態(tài)添加新的根級(jí)響應(yīng)式屬性役耕。如果需要修改以及實(shí)例化的屬性采转,需要使用vue.set()方法。所以我們對(duì)store.js進(jìn)行如下的修改
//store.js
INIT_DATA(state){
if (window.localStorage.getItem('vue-note')) {
const oldState = JSON.parse(window.localStorage.getItem('vue-note'))
Vue.set(state, 'notes', oldState.notes)
Vue.set(state, 'activeNote', oldState.activeNote)
}
},
SAVE_DATA(state){
window.localStorage.setItem('vue-note', JSON.stringify(state))
}
這樣瞬痘,就完美實(shí)現(xiàn)了本地存儲(chǔ)功能氏义。