請各位讀者添加一下作者的微信公眾號荒澡,以后有新的文章,將在微信公眾號直接推送給各位与殃,非常感謝单山。
0.前言
最近在看 Vue 的時候碍现,別人給我安利了一個國外的小案例,通過 Vue 和 Vuex 來實現(xiàn)一個記事本米奸。
仔細(xì)剖析下昼接,發(fā)現(xiàn)“麻雀雖小,五臟俱全”悴晰,是一個挺適合初學(xué)者學(xué)習(xí)分析的一個案例慢睡,所以自己也將自己的學(xué)習(xí)過程整理,得出本文铡溪。
國際慣例漂辐,首先感謝原文作者。
參考案例傳送門:
之后是內(nèi)容聲明:
- 原文是2016年 4 月 20 日就出現(xiàn)了的佃却,所以很多小伙伴可能已經(jīng)看過了者吁,但是本文的實現(xiàn)過程卻和原文不同,所以饲帅,你其實也可以重新看一看的#斜眼笑复凳。
- 本文僅用于作者記錄使用,請勿轉(zhuǎn)載灶泵,請隨意吐槽育八。
另請注意,很多童鞋一直在問我赦邻,為什么粘貼完代碼無效髓棋,或者報錯的。
請在使用前安裝環(huán)境惶洲。
另作者已經(jīng)將完整程序包放在 Git 上了按声,請點擊下方鏈接進行下載,別忘了給我個 Star 呀恬吕!笑签则。
好了,開始正文铐料。
1. 前期準(zhǔn)備
本文中使用了以下內(nèi)容渐裂,在閱讀本文前,請保證您對以下內(nèi)容有了基礎(chǔ)的了解钠惩。
- Vue
- Vuex
- Vue-cli
- ES6
之前作者寫過一篇關(guān)于 Vue 基礎(chǔ)入門的文章柒凉。
里面介紹了一下關(guān)于 Vue 的發(fā)展前景,以及 Vue 最基礎(chǔ)的使用篓跛,大家可以選擇性的閱讀一下膝捞。
2.需求分析
首先,如果我們想要制作一個單頁應(yīng)用愧沟,我們首先要知道绑警,我們要做什么求泰?
那么,首先來一個草圖计盒。
這時候,我們一起來分析一下芽丹,當(dāng)前頁面的實現(xiàn)過程北启。
- 頁面中分為三個部分
- 左側(cè)工具欄:Toolbar
- 中間筆記列表:NoteList
- 右側(cè)編輯區(qū)域:Editor
- 頁面樣式的設(shè)置
- 在頁面的實現(xiàn)過程中,需要完成以下幾個方法
- 新增筆記
- 修改筆記
- 刪除筆記
- 切換筆記的收藏與取消收藏
- 切換顯示數(shù)據(jù)列表類型
- 設(shè)置當(dāng)前激活的筆記
這時候我們明確了當(dāng)前內(nèi)容至少會涉及到頁面拔第,頁面美化咕村,以及數(shù)據(jù)處理等。
那我們就可以針對特定的需求來進行特定內(nèi)容的處理了蚊俺。
- 結(jié)構(gòu):用 Vue-cli 來快速生成
- 美化:想了想懈涛,還是自己手動寫吧
- 數(shù)據(jù):選用 Vuex 來集中處理
但是在正式開始文章前,請先了解一下泳猬,關(guān)于 Vuex 的基礎(chǔ)知識批钠。
Vuex 是一個專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式。
它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài)得封,并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化埋心。
這就是 Vuex 背后的基本思想,借鑒了 Flux忙上、Redux拷呆、和 The Elm Architecture。
與其他模式不同的是疫粥,Vuex 是專門為 Vue.js 設(shè)計的狀態(tài)管理庫茬斧,以利用 Vue.js 的細(xì)粒度數(shù)據(jù)響應(yīng)機制來進行高效的狀態(tài)更新。
在這里有一個需要注意的內(nèi)容梗逮,就是關(guān)于 Vuex 中的 Store项秉。
每一個 Vuex 應(yīng)用的核心就是 store(倉庫)。"store" 基本上就是一個容器库糠,它包含著你的應(yīng)用中大部分的狀態(tài)(state)伙狐。Vuex 和單純的全局對象有以下兩點不同:
- Vuex 的狀態(tài)存儲是響應(yīng)式的。當(dāng) Vue 組件從 store 中讀取狀態(tài)的時候瞬欧,若 store 中的狀態(tài)發(fā)生變化贷屎,那么相應(yīng)的組件也會相應(yīng)地得到高效更新。
- 你不能直接改變 store 中的狀態(tài)艘虎。改變 store 中的狀態(tài)的唯一途徑就是顯式地提交(commit) mutations唉侄。這樣使得我們可以方便地跟蹤每一個狀態(tài)的變化,從而讓我們能夠?qū)崿F(xiàn)一些工具幫助我們更好地了解我們的應(yīng)用野建。
其實說白了属划,我們的 state 就是我們項目中所有數(shù)據(jù)的集合恬叹,之后通過 Vuex 來區(qū)分開實際應(yīng)用中的 組件本地狀態(tài)和應(yīng)用層級狀態(tài)。
這里需要區(qū)分一下同眯,關(guān)于 組件本地狀態(tài)和應(yīng)用層級狀態(tài)绽昼。
- 組件本地狀態(tài)
- state that is used by only one component (think of the data option you pass to a Vue component)
- 該狀態(tài)表示僅僅在組件內(nèi)部使用的狀態(tài),有點類似通過配置選項傳入 Vue 組件內(nèi)部的意思
- 應(yīng)用層級狀態(tài)
- state that is used by more than one component
- 應(yīng)用層級狀態(tài)须蜗,表示同時被多個組件共享的狀態(tài)層級
如果你明白了上面的內(nèi)容硅确,那么接下來,我們就可以一起來構(gòu)建我們的新項目了明肮。
3.項目構(gòu)建
項目推薦直接使用 Vue 官方提供的腳手架(Vue-cli)菱农,所以第一步首先是安裝腳手架。
PS: 作者默認(rèn)大家是對 Vue 有一定的基礎(chǔ)了解之后再看的文本柿估,所以如果有哪些步驟不明確循未,請參考 Vue - 起手式。
安裝 Vue-cli
npm install -g vue-cli
注意:
-
-g
是直接安裝在全局環(huán)境下秫舌,推薦大家也是如此的妖。 - 推薦大家確認(rèn)一下自己當(dāng)前 node 的版本,盡量是最新版舅巷。
- 如果發(fā)生無法安裝羔味,請確認(rèn)是否是權(quán)限不足。
- 如果是權(quán)限不足钠右,請在內(nèi)容前加上
sudo
sudo npm install -g vue-cli
- 如果是權(quán)限不足钠右,請在內(nèi)容前加上
創(chuàng)建應(yīng)用
vue init webpack note
-
webpack
是我們安裝內(nèi)容時所默認(rèn)使用的模板赋元。 -
note
是我們創(chuàng)建的項目名稱 - 安裝過程中,會出現(xiàn)詢問你具體項目信息的內(nèi)容
- 推薦大家都直接選擇拒絕即可飒房。
- 詢問內(nèi)容:項目名搁凸,描述,作者三項狠毯,直接回車即可
- 檢查測試:語法檢查护糖,單元測試,項目測試三項直接輸入
N
- 推薦大家都直接選擇拒絕即可飒房。
進入當(dāng)前目錄
cd /Users/lp1/Desktop/notes (你當(dāng)前的文件目錄)
安裝 Vue 的依賴包
npm install
如果不安裝依賴嚼松,經(jīng)常會發(fā)生下面這種錯誤嫡良。
啟動 Vue 服務(wù)
npm run dev
在啟動服務(wù)的時候,也有可能會遇到 端口被占用 的錯誤献酗。
第一種解決方案是進入 Vue 中的 index.js 中修改 默認(rèn)端口號寝受。
第二種是自己去找到被占用的端口,kill 掉它(一般 kill node 的就可以)罕偎。
如果這時候頁面中已經(jīng)彈出一個新的頁面很澄,則證明你當(dāng)前的服務(wù)啟動成功了。
這里就不單純的介紹項目的內(nèi)容組成了,具體的可以參考我之前的文章甩苛。
4. 項目組件劃分
在開始之前蹂楣,就如我們上面的分析一般,我們需要將我們所要使用的內(nèi)容進行劃分讯蒲。
作者留言:
Vue 中最重要的兩個概念痊土,理解了這兩個概念對以后會有很大幫助。
- 模塊化編程
- 數(shù)據(jù)驅(qū)動
根據(jù)頁面中的功能爱葵,我們可以將頁面分成四個大塊施戴。
首先第一個肯定是最外層的父級,我們一般直接書寫在 App.vue
當(dāng)中萌丈。
其次是左中右三部分的組件,我們分別命名并統(tǒng)一放在 components
當(dāng)中雷则。
- Toolbar : 工具欄用于對當(dāng)前內(nèi)容進行新增和刪除
- NoteList : 列表通過操作 CSS 來高亮我們選中的內(nèi)容
- Editor : 編輯器用于顯示用戶的編輯操作
而最下面的 App.vue 則是所有組件的根辆雾。
那我們現(xiàn)在雖然將不同的組件進行了劃分,可以劃分之后我們該如何去處理三個組件之間的通信呢月劈?
這時候其實就該我們的 Vuex 出馬了度迂,Vuex 作為一個“數(shù)據(jù)中心”,我們可以提前將我們想要的內(nèi)容猜揪,進行提前設(shè)置惭墓。
5.狀態(tài)管理
著重強調(diào):
vuex 中數(shù)據(jù)是單向的,只能從 store 獲取而姐,而我們的各種操作也始終都在 store.js 中維護腊凶,并以此來給其他組件公用。
那根據(jù)我們上面所說拴念,我們需要在 Vuex 文件夾下钧萍,創(chuàng)建一個 store.js
文件。
需要注意政鼠,這里使用很多 ES6 的語法风瘦,并且采用了原文不同的實現(xiàn)方法。
//引入vue及vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//需要維護的狀態(tài)
const state = {
/*
notes:存儲note項
activeNote:當(dāng)前正在編輯的note項
*/
notes:[],
activeNote:{}
}
const mutations = {
//添加筆記
ADD_NOTE(state){
const newNote = {
/*
text:默認(rèn)文字內(nèi)容
favorite:收藏
*/
text:"new Note",
favorite:false
}
state.notes.push(newNote)
state.activeNote = newNote
},
//編輯筆記
EDIT_NOTE(state,text){
state.activeNote.text = text
},
// 設(shè)置當(dāng)前激活的筆記
SET_ACTIVE_NOTE(state,note){
state.activeNote = note
},
// 切換筆記的收藏與取消收藏
TOGGLE_FAVORITE(state){
state.activeNote.favorite = !state.activeNote.favorite
},
//刪除筆記
DELETE_NOTE(state){
for (var i=0; i<state.notes.length; i++){
if (state.notes[i] == state.activeNote){
state.notes.splice(i, 1)
}
}
state.activeNote = state.notes[0]
}
}
const actions = {
/*
actions處理函數(shù)接受一個 context 對象
{
state, // 等同于 store.state, 若在模塊中則為局部狀態(tài)
rootState, // 等同于 store.state, 只存在于模塊中
commit, // 等同于 store.commit
dispatch, // 等同于 store.dispatch
getters // 等同于 store.getters
}
*/
addNote({commit}){
commit('ADD_NOTE')
},
editNote({commit},text){
commit("EDIT_NOTE",text)
},
updateActiveNote({commit},note){
commit('SET_ACTIVE_NOTE',note)
},
toggleFavorite({commit}){
commit('TOGGLE_FAVORITE')
},
deleteNote({commit}){
commit('DELETE_NOTE')
}
}
const getters = {
/*
Getters 接受 state 作為其第一個參數(shù)
state => state.notes為箭頭函數(shù)等價于:
function (state){
return state.notes
}
*/
notes: state => state.notes,
activeNote: state => state.activeNote
}
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
記得處理完我們所需要的數(shù)據(jù)之后公般,在 main.js
當(dāng)中將我們的 store
添加上去万搔。
import Vue from 'vue'
import App from './App'
import store from '../vuex/store'
Vue.config.productionTip = false
new Vue({
el: '#app',
store,
template: '<App/>',
components: { App }
})
6. 根組件
對于整個 APP 的根,也就是 App.vue
來說官帘,它需要處理的事情非常簡單瞬雹,就是在對應(yīng)的位置去調(diào)用對應(yīng)的組件即可。
<template>
<div id="app">
<toolbar></toolbar>
<note-list></note-list>
<editor></editor>
</div>
</template>
<!--
李鵬 QQ:3206064928
-->
<script>
import Toolbar from './components/Toolbar'
import NoteList from './components/NoteList'
import Editor from './components/Editor'
export default {
components:{
Toolbar,
NoteList,
Editor
}
}
</script>
<style type="text/css">
html, #app {
height: 100%;
}
body {
margin: 0;
padding: 0;
border: 0;
height: 100%;
max-height: 100%;
position: relative;
}
</style>
至于調(diào)用的組件內(nèi)部遏佣,具體是如何實現(xiàn)的 App.vue
并不關(guān)心挖炬。
7. Toolbar.vue
關(guān)于 Toolbar.vue
的設(shè)置就比較簡單了,我們只需要調(diào)用我們之前設(shè)置好的內(nèi)容就可以。
<template>
<div id="toolbar">
<i @click="addOne" class="glyphicon glyphicon-plus"></i>
<i @click="toggleFavorite" class="glyphicon glyphicon-star" v-bind:class="{starred:activeNote.favorite}"></i>
<i @click="deleteNote" class="glyphicon glyphicon-remove"></i>
</div>
</template>
<script>
export default {
computed:{
activeNote(){
return this.$store.getters.activeNote
}
},
methods:{
addOne(){
//通過dispatch分發(fā)到actions中的addNote
this.$store.dispatch('addNote')
},
toggleFavorite(){
this.$store.dispatch('toggleFavorite')
},
deleteNote(){
this.$store.dispatch('deleteNote')
}
}
}
</script>
<style type="text/css">
#toolbar {
float: left;
width: 80px;
height: 100%;
background-color: #30414D;
color: #767676;
padding: 35px 25px 25px 25px;
}
#toolbar i {
font-size: 30px;
margin-bottom: 35px;
cursor: pointer;
opacity: 0.8;
transition: opacity 0.5s ease;
}
#toolbar i:hover {
opacity: 1;
}
.starred {
color: #F7AE4F;
}
</style>
需要注意意敛,在這里馅巷,我調(diào)用了一下 bootstrap
的圖標(biāo)樣式。
這個是在 index.js
當(dāng)中調(diào)用的草姻。
8. NoteList.vue
由于我們之前已經(jīng)將關(guān)于數(shù)據(jù)部分的內(nèi)容處理過了钓猬,所以在這里,我們只需要進行一下簡單的判斷撩独,將特定的內(nèi)容加載即可敞曹。
<template>
<div id="notes-list">
<div id="list-header">
<h2>Notes</h2>
<div class="btn-group btn-group-justified" role="group">
<!-- All Notes button -->
<div class="btn-group" role="group">
<button @click="show='all'" type="button" class="btn btn-default" v-bind:class="{active:show=='all'}">
All Notes
</button>
</div>
<!-- Favorites Button -->
<div class="btn-group" role="group">
<button @click="show='favorites'" type="button" class="btn btn-default" v-bind:class="{active:show=='favorites'}">
Favorites
</button>
</div>
</div>
</div>
<!-- render notes in a list -->
<div class="container">
<div class="list-group">
<a v-for="item in notes" class="list-group-item" v-bind:class="{active:activeNote == item}" v-on:click="updateActiveNote(item)" href="#">
<h4 class="list-group-item-heading">
{{item.text}}
</h4>
</a>
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
show:'all'
}
},
computed:{
notes(){
if (this.show=='all'){
return this.$store.getters.notes
}else if(this.show=='favorites'){
return this.$store.getters.notes.filter(note=>note.favorite)
}
},
activeNote(){
return this.$store.getters.activeNote
}
},
methods:{
updateActiveNote(note){
console.log(note)
this.$store.dispatch('updateActiveNote',note)
}
}
}
</script>
<style type="text/css">
#notes-list {
float: left;
width: 300px;
height: 100%;
background-color: #F5F5F5;
font-family: 'Raleway', sans-serif;
font-weight: 400;
}
#list-header {
padding: 5px 25px 25px 25px;
}
#list-header h2 {
font-weight: 300;
text-transform: uppercase;
text-align: center;
font-size: 22px;
padding-bottom: 8px;
}
#notes-list .container {
height: calc(100% - 137px);
max-height: calc(100% - 137px);
overflow: auto;
width: 100%;
padding: 0;
}
#notes-list .container .list-group-item {
border: 0;
border-radius: 0;
}
.list-group-item-heading {
font-weight: 300;
font-size: 15px;
}
</style>
9. Editor.vue
關(guān)于編輯區(qū)域,只需要做一件事综膀,就是獲取當(dāng)前對應(yīng)內(nèi)容的文字即可澳迫。
<template>
<div id="note-editor">
<textarea v-bind:value="activeNoteText" v-on:input="editNote" class="form-control"></textarea>
</div>
</template>
<script>
export default {
computed:{
activeNoteText(){
return this.$store.getters.activeNote.text
}
},
methods:{
editNote(e){
this.$store.dispatch('editNote',e.target.value)
}
}
}
</script>
<style type="text/css">
#note-editor {
height: 100%;
margin-left: 380px;
}
#note-editor textarea {
height: 100%;
border: 0;
border-radius: 0;
}
</style>
10.后記
本文主要是用于記錄一下自己的分析過程,如果有哪里出錯了剧劝,歡迎大家指出橄登。
謝謝大家。
李鵬(MR_LP)
2017年04月17日