實例 - Vue 單頁應(yīng)用:記事本

請各位讀者添加一下作者的微信公眾號荒澡,以后有新的文章,將在微信公眾號直接推送給各位与殃,非常感謝单山。


0.前言


最近在看 Vue 的時候碍现,別人給我安利了一個國外的小案例,通過 Vue 和 Vuex 來實現(xiàn)一個記事本米奸。

仔細(xì)剖析下昼接,發(fā)現(xiàn)“麻雀雖小,五臟俱全”悴晰,是一個挺適合初學(xué)者學(xué)習(xí)分析的一個案例慢睡,所以自己也將自己的學(xué)習(xí)過程整理,得出本文铡溪。

國際慣例漂辐,首先感謝原文作者。

參考案例傳送門:

Learn Vuex by Building a Notes App

之后是內(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ǔ)的了解钠惩。

之前作者寫過一篇關(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

創(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日

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末讥此,一起剝皮案震驚了整個濱河市拢锹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萄喳,老刑警劉巖卒稳,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異他巨,居然都是意外死亡充坑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門闻蛀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匪傍,“玉大人,你說我怎么就攤上這事觉痛∫酆猓” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵薪棒,是天一觀的道長手蝎。 經(jīng)常有香客問我,道長俐芯,這世上最難降的妖魔是什么棵介? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮吧史,結(jié)果婚禮上邮辽,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好吨述,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布岩睁。 她就那樣靜靜地躺著,像睡著了一般揣云。 火紅的嫁衣襯著肌膚如雪捕儒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天邓夕,我揣著相機與錄音刘莹,去河邊找鬼。 笑死焚刚,一個胖子當(dāng)著我的面吹牛点弯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矿咕,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼蒲拉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了痴腌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤燃领,失蹤者是張志新(化名)和其女友劉穎士聪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猛蔽,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡剥悟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了曼库。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片区岗。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖毁枯,靈堂內(nèi)的尸體忽然破棺而出慈缔,到底是詐尸還是另有隱情,我是刑警寧澤种玛,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布藐鹤,位于F島的核電站,受9級特大地震影響赂韵,放射性物質(zhì)發(fā)生泄漏娱节。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一祭示、第九天 我趴在偏房一處隱蔽的房頂上張望肄满。 院中可真熱鬧,春花似錦、人聲如沸稠歉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轧抗。三九已至恩敌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間横媚,已是汗流浹背纠炮。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灯蝴,地道東北人恢口。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像穷躁,于是被迫代替她去往敵國和親耕肩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 1问潭,今天我學(xué)會了 1.公共樣式的提取 2.css2d轉(zhuǎn)換 配合transform屬性使用 2.1translate...
    613桑閱讀 341評論 0 0
  • 關(guān)于化妝品,其實我們每個人的理解都是不一樣的灾茁,每個國家對它的定義也不同窜觉,甚至不同國家對化妝品在法律法規(guī)上的限制,也...
    真愛藍(lán)閱讀 5,758評論 0 1
  • 文 / 糖三角先森 本文的誕生離不開好友 @城璃茉 的幫助北专,并且她花了大量工作個人時間對本文做出批注和適當(dāng)?shù)男薷模?..
    糖三角先森閱讀 268評論 1 3
  • 宇彤老師第三期開營了:通過昨晚的學(xué)習(xí)禀挫,開始反思自已的發(fā)音、吸氣拓颓、呼氣.......開始慢慢體會美麗智慧的宇彤老師"...
    云淡風(fēng)輕一閱讀 198評論 0 0