實例 - Vue 單頁應用:記事本

請各位讀者添加一下做者的微信公衆號,之後有新的文章,將在微信公衆號直接推送給各位,很是感謝。
javascript

0.前言


若文章中存在內容沒法加載的狀況,請移步做者其餘博客。css

最近在看 Vue 的時候,別人給我安利了一個國外的小案例,經過 Vue 和 Vuex 來實現一個記事本。html

仔細剖析下,發現「麻雀雖小,五臟俱全」,是一個挺適合初學者學習分析的一個案例,因此本身也將本身的學習過程整理,得出本文。vue

國際慣例,首先感謝原文做者。java

參考案例傳送門:node

Learn Vuex by Building a Notes Appwebpack

以後是內容聲明:git

  • 原文是2016年 4 月 20 日就出現了的,因此不少小夥伴可能已經看過了,可是本文的實現過程卻和原文不一樣,因此,你其實也能夠從新看一看的#斜眼笑。
  • 本文僅用於做者記錄使用,請勿轉載,請隨意吐槽。

另請注意,不少童鞋一直在問我,爲何粘貼完代碼無效,或者報錯的。es6

請在使用前安裝環境。github

另做者已經將完整程序包放在 Git 上了,請點擊下方連接進行下載,別忘了給我個 Star 呀!笑。

好了,開始正文。

1. 前期準備


本文中使用瞭如下內容,在閱讀本文前,請保證您對如下內容有了基礎的瞭解。

以前做者寫過一篇關於 Vue 基礎入門的文章。

裏面介紹了一下關於 Vue 的發展前景,以及 Vue 最基礎的使用,你們能夠選擇性的閱讀一下。

2.需求分析


首先,若是咱們想要製做一個單頁應用,咱們首先要知道,咱們要作什麼?

那麼,首先來一個草圖。

這時候,咱們一塊兒來分析一下,當前頁面的實現過程。

  • 頁面中分爲三個部分
    • 左側工具欄:Toolbar
    • 中間筆記列表:NoteList
    • 右側編輯區域:Editor
  • 頁面樣式的設置
  • 在頁面的實現過程當中,須要完成如下幾個方法
    • 新增筆記
    • 修改筆記
    • 刪除筆記
    • 切換筆記的收藏與取消收藏
    • 切換顯示數據列表類型
    • 設置當前激活的筆記

這時候咱們明確了當前內容至少會涉及到頁面,頁面美化,以及數據處理等。

那咱們就能夠針對特定的需求來進行特定內容的處理了。

  • 結構:用 Vue-cli 來快速生成
  • 美化:想了想,仍是本身手動寫吧
  • 數據:選用 Vuex 來集中處理

可是在正式開始文章前,請先了解一下,關於 Vuex 的基礎知識。

Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式
它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
這就是 Vuex 背後的基本思想,借鑑了 FluxRedux、和 The Elm Architecture
與其餘模式不一樣的是,Vuex 是專門爲 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。

在這裏有一個須要注意的內容,就是關於 Vuex 中的 Store。

每個 Vuex 應用的核心就是 store(倉庫)。"store" 基本上就是一個容器,它包含着你的應用中大部分的狀態(state)。Vuex 和單純的全局對象有如下兩點不一樣:

  • Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那麼相應的組件也會相應地獲得高效更新。
  • 你不能直接改變 store 中的狀態。改變 store 中的狀態的惟一途徑就是顯式地提交(commit) mutations。這樣使得咱們能夠方便地跟蹤每個狀態的變化,從而讓咱們可以實現一些工具幫助咱們更好地瞭解咱們的應用。

其實說白了,咱們的 state 就是咱們項目中全部數據的集合,以後經過 Vuex 來區分開實際應用中的 組件本地狀態應用層級狀態

這裏須要區分一下,關於 組件本地狀態應用層級狀態

  • 組件本地狀態
    • state that is used by only one component (think of the data option you pass to a Vue component)
    • 該狀態表示僅僅在組件內部使用的狀態,有點相似經過配置選項傳入 Vue 組件內部的意思
  • 應用層級狀態
    • state that is used by more than one component
    • 應用層級狀態,表示同時被多個組件共享的狀態層級

若是你明白了上面的內容,那麼接下來,咱們就能夠一塊兒來構建咱們的新項目了。

3.項目構建


項目推薦直接使用 Vue 官方提供的腳手架(Vue-cli),因此第一步首先是安裝腳手架。

PS: 做者默認你們是對 Vue 有必定的基礎瞭解以後再看的文本,因此若是有哪些步驟不明確,請參考 Vue - 起手式

安裝 Vue-cli

npm install -g vue-cli複製代碼

注意:

  • -g 是直接安裝在全局環境下,推薦你們也是如此。
  • 推薦你們確認一下本身當前 node 的版本,儘可能是最新版。
  • 若是發生沒法安裝,請確認是不是權限不足。
    • 若是是權限不足,請在內容前加上 sudo
    • sudo npm install -g vue-cli

建立應用

vue init webpack note複製代碼
  • webpack 是咱們安裝內容時所默認使用的模板。
  • note 是咱們建立的項目名稱
  • 安裝過程當中,會出現詢問你具體項目信息的內容
    • 推薦你們都直接選擇拒絕便可。
      • 詢問內容:項目名,描述,做者三項,直接回車便可
      • 檢查測試:語法檢查,單元測試,項目測試三項直接輸入 N

進入當前目錄

cd /Users/lp1/Desktop/notes    (你當前的文件目錄)複製代碼

安裝 Vue 的依賴包

npm install複製代碼

若是不安裝依賴,常常會發生下面這種錯誤。

啓動 Vue 服務

npm run dev複製代碼

在啓動服務的時候,也有可能會遇到 端口被佔用 的錯誤。

第一種解決方案是進入 Vue 中的 index.js 中修改 默認端口號。

第二種是本身去找到被佔用的端口,kill 掉它(通常 kill node 的就能夠)。

若是這時候頁面中已經彈出一個新的頁面,則證實你當前的服務啓動成功了。

這裏就不單純的介紹項目的內容組成了,具體的能夠參考我以前的文章。

4. 項目組件劃分


在開始以前,就如咱們上面的分析通常,咱們須要將咱們所要使用的內容進行劃分。

做者留言:
Vue 中最重要的兩個概念,理解了這兩個概念對之後會有很大幫助。

  • 模塊化編程
  • 數據驅動

根據頁面中的功能,咱們能夠將頁面分紅四個大塊。

首先第一個確定是最外層的父級,咱們通常直接書寫在 App.vue當中。

其次是左中右三部分的組件,咱們分別命名並統一放在 components 當中。

  • Toolbar : 工具欄用於對當前內容進行新增和刪除
  • NoteList : 列表經過操做 CSS 來高亮咱們選中的內容
  • Editor : 編輯器用於顯示用戶的編輯操做

而最下面的 App.vue 則是全部組件的根。

那咱們如今雖然將不一樣的組件進行了劃分,能夠劃分以後咱們該如何去處理三個組件之間的通訊呢?

這時候其實就該咱們的 Vuex 出馬了,Vuex 做爲一個「數據中心」,咱們能夠提早將咱們想要的內容,進行提早設置。

5.狀態管理


着重強調:
vuex 中數據是單向的,只能從 store 獲取,而咱們的各類操做也始終都在 store.js 中維護,並以此來給其餘組件公用。

那根據咱們上面所說,咱們須要在 Vuex 文件夾下,建立一個 store.js 文件。

須要注意,這裏使用不少 ES6 的語法,而且採用了原文不一樣的實現方法。

//引入vue及vuex
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

//須要維護的狀態
const state = {
    /* notes:存儲note項 activeNote:當前正在編輯的note項 */
    notes:[],
    activeNote:{}
}

const mutations = {
    //添加筆記
    ADD_NOTE(state){
        const newNote = {
            /* text:默認文字內容 favorite:收藏 */
            text:"new Note",
            favorite:false
        }
        state.notes.push(newNote)
        state.activeNote = newNote
    },
    //編輯筆記
    EDIT_NOTE(state,text){
        state.activeNote.text = text
    },
    // 設置當前激活的筆記
    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處理函數接受一個 context 對象 { state, // 等同於 store.state, 若在模塊中則爲局部狀態 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 做爲其第一個參數 state => state.notes爲箭頭函數等價於: function (state){ return state.notes } */
  notes: state => state.notes,
  activeNote: state => state.activeNote
}

export default new Vuex.Store({
    state,
    mutations,
    actions,
    getters
})複製代碼

記得處理完咱們所須要的數據以後,在 main.js 當中將咱們的 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 來講,它須要處理的事情很是簡單,就是在對應的位置去調用對應的組件便可。

<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>複製代碼

至於調用的組件內部,具體是如何實現的 App.vue 並不關心。

7. Toolbar.vue


關於 Toolbar.vue 的設置就比較簡單了,咱們只須要調用咱們以前設置好的內容就能夠。

<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分發到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>複製代碼

須要注意,在這裏,我調用了一下 bootstrap 的圖標樣式。

這個是在 index.js 當中調用的。

8. NoteList.vue


因爲咱們以前已經將關於數據部分的內容處理過了,因此在這裏,咱們只須要進行一下簡單的判斷,將特定的內容加載便可。

<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


關於編輯區域,只須要作一件事,就是獲取當前對應內容的文字便可。

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

相關文章
相關標籤/搜索