天天看點

探索 vuex 2.0 以及使用 vuejs 2.0 + vuex 2.0 建構記事本應用

探索 vuex 2.0 以及使用 vuejs 2.0 + vuex 2.0 建構記事本應用

前言

首先說明這并不是一個教程貼,而記事本應用是網上早有的案例,對于學習 vuex 非常有幫助。我的目的是探索 vuex 2.0 ,然後使用 vuejs 2.0 + vuex 2.0 重寫這個應用,其中最大的問題是使用 vue-cli 建構應用時遇到的問題。通過這些問題深入探索

vue

以及

vuex

我對于架構的學習一直斷斷續續,最先接觸的是

react

,是以有一些先入為主的觀念,喜歡

react

更多一點,尤其在應用的建構層面來說。之是以斷斷續續,是因為自己 JS 基礎較弱,剛開始學習的時候,隻是比着葫蘆畫瓢,雖然可以做出點東西,但對于其中的一些概念仍然雲裡霧裡,不知所雲,無法深入了解架構。是以我又臨時放棄架構的學習,開始學習 JS 基礎。事實證明打牢基礎之後,學習架構以及了解架構是神速的。而學習 webgl 和 three.js 的過程與此類似。沒有 webgl 的基礎,學習 three.js 隻會停留在初級階段。

我在過去的半年參加了很多面試,幾乎無一例外的都會被問架構的使用情況,但是其中很多公司屬于随波逐流,使用架構比較盲目。甚至覺得使用架構是極其高大上的事情。雖然我學習過架構,但畢竟沒有深入學習也沒有拿得出手的項目,是以隻是隻言片語的說兩句,大部分知識是懵懂的。然而面對面試官不屑的神情以及以此作為選拔的名額,心想這樣的面試官太膚淺。當然很多公司的面試還是以基礎為主。架構屬于探索,互相學習的狀态。我在這篇文章中強調一點,學習能力以及解決問題的能力更重要。

開始吧

言歸正傳,對于這個筆記本案例,大家可以直接百度搜 vue notes ,這是一篇英文教程,大家看到的都是翻譯的。在剛開始 vue 資料稀缺的時候,這樣的文章非常珍貴。demo 點這裡。說白了,算是

todoMVC

案例的一個變體。當初覺得這個例子非常好,想跟着學一學,結果一拖半年過去了。這幾天終于抽時間把這個例子敲了一遍。學習在于舉一反三。如果大家按照網上教程來做,那麼 NPM 包預設安裝的都是最新版本,運作會報錯。是以如果用 vuex 2 要怎麼寫呢?

以下是 notes-vuex-app 的源檔案目錄:

探索 vuex 2.0 以及使用 vuejs 2.0 + vuex 2.0 建構記事本應用

在使用 vue 2 重寫這個 app 之前,我在想能不能不改變檔案目錄結構以及配置位置呢?就是用比較生硬的方式重寫,或者說單純的文法修改。事實是可行的,否則我就不會寫這篇文章了。然而面對的問題非常多,但卻是以深入的了解了 vue 以及 vuex。最大的問題是 webpack 的建構,如果使用 webpack 2.0+的話,坑比較多。本人是菜鳥,是以最終選擇了 vue-cli 提供的兩個 webpack 的模闆,分别是

webpack-simple

webpack

,我先使用

webpack-simple

,它和原 app 的結構基本吻合。目錄如下:

探索 vuex 2.0 以及使用 vuejs 2.0 + vuex 2.0 建構記事本應用

使用 vue-cli 生成基本目錄之後,再安裝 vuex2 。

main.js 的小改動

原示例 main.js 如下所示,但運作出錯了,主要是 Vue 2 的根執行個體渲染稍有變化

import Vue from 'vue'     import store from './vuex/store'     import App from './components/App.vue'     new Vue({       store, // 注入到所有子元件       el: 'body',       components: { App }     })      

改正之後:

import Vue from 'vue'     import store from './vuex/store'     import App from './components/App.vue'     new Vue({         store, // inject store to all children         el: '#app',         template: '<App/>',         components: { App }     })      

或者

import Vue from 'vue'     import store from './vuex/store'     import App from './components/App.vue'     new Vue({         store, // inject store to all children         el: '#app',         render: h => h(App)     })      

vuex 2 的變化

這個應用改寫的主要問題集中在 vuex 2 的變化上,這些變化确實會讓人感到淩亂,我無數次抓耳撓腮的罵娘。不過通過官方給出的示例也可以看出一些端倪。

首先是

action.js

,隻需注意一點,所有的

dispatch

都要改成

commit

export const addNote = ({ commit }) => {       commit('ADD_NOTE')     }     export const editNote = ({ commit }, e) => {       commit('EDIT_NOTE', e.target.value)     }     export const deleteNote = ({ commit }) => {       commit('DELETE_NOTE')     }     export const updateActiveNote = ({ commit }, note) => {       commit('SET_ACTIVE_NOTE', note)     }     export const toggleFavorite = ({ commit }) => {       commit('TOGGLE_FAVORITE')     }      

store.js

變化也不大,但是要注意幾個地方:

import Vue from 'vue'     import Vuex from 'vuex'     import * as actions from './actions'     Vue.use(Vuex)     const state = {       notes: [],       activeNote: {}     }     const mutations = {       ADD_NOTE (state) {         const newNote = {           text: 'New note',           favorite: false         }         state.notes.push(newNote)         state.activeNote = newNote       },       EDIT_NOTE (state, text) {         state.activeNote.text = text       },       DELETE_NOTE (state) {         state.notes.splice(state.notes.indexOf(state.activeNote),1)         state.activeNote = state.notes[0] || {}       },       TOGGLE_FAVORITE (state) {         state.activeNote.favorite = !state.activeNote.favorite       },       SET_ACTIVE_NOTE (state, note) {         state.activeNote = note       }     }     const getters = {       notes: state => state.notes,       activeNote: state => state.activeNote,       activeNoteText: state => state.activeNote.text     }     export default new Vuex.Store({       state,       mutations,       actions,       getters     })      

原示例檔案中沒有将

getters

寫到 store.js 中,而是直接在元件中定義的。為了更清晰,我仿照官方示例也提取出來寫在了 store.js 中,這樣在元件中調用時比較友善。其次也引入了 action.js,并作為

actions

對象傳遞給

Vuex.store()

,這算是 vuex 的标準寫法吧,對于後面在元件中調用比較有利。

其中要注意

DELETE_NOTE (state){}

這個方法,原示例使用了 vue1 提供的

remove

方法,但是 vue2 中去掉了這個方法。仔細想想就會明白,這個函數的作用就是删除 notes 數組中的元素。可以使用原生的

splice

方法。如果 JS 基礎紮實的話,這裡應該很好了解,沒有什麼大問題。其次相比原示例,添加一個删除後操作的判斷。

我之前一直不太了解 flux 的概念,感覺像是新東西,完全不知道它的目的及作用。換成 Vuex,還是有點稀裡糊塗。但是通過修改這個示例,基本算是開竅了。這些東西本身并沒有玄機奧妙,想一想,如果我們不用架構,而是自己手寫一個 todoMVC 時要怎麼做?應該也是這樣的思路,定義一個

notes

數組變量以及

activeNote

的變量。然後在建立一些改變狀态的方法。我在面試中遇到過一個情況,面試官反複問我為什麼需要使用架構,用 jQuery 不是也可以實作嗎?這樣說确實沒錯,用比較原始的方法當然可以做,隻是代碼結構會備援或者淩亂,缺少小而美的特點。架構以及設計模式對代碼做了整合封裝,對于一個 CURD 應用比較友好,實作起來更友善更簡單。我對于 Vuex 的了解就是,它是一個對象,封裝了與狀态相關的方法和屬性。而所謂的狀态就是點選、按鍵等操作之後的變化。

元件中使用 vuex

先看一下

Toolbar.vue

這個元件。修改後的代碼如下:

<template>       <div id="toolbar">         <i @click="addNote" class="glyphicon glyphicon-plus"></i>         <i @click="toggleFavorite"           class="glyphicon glyphicon-star"           :class="{starred: activeNote.favorite}"></i>         <i @click="deleteNote" class="glyphicon glyphicon-remove"></i>       </div>     </template>     <script>     import { mapGetters, mapActions } from 'vuex'     export default {       computed: mapGetters([         'activeNote'       ]),       methods: {         ...mapActions([           'addNote',           'deleteNote',           'toggleFavorite'         ])       }     }     </script>      

通過和原示例代碼對比,這裡的差別一目了然。我通過在控制台列印 Vue 執行個體,折騰很長時間才大體明白怎麼回事。

vuex 1

在元件中使用時會直接将

getters

actions

挂到 vuex 這個屬性上,沒有提供

mapGetters

mapActions

等一些方法。而 vuex2 使用

mapGetters

mapActions

等一些方法将 actions 的方法挂到 Vue 執行個體上。總的來說,都是把 actions 的方法挂到 Vue 執行個體上。我們從這個層面上談談 Vue 執行個體,Vue 2 的變化就是其屬性的變化。比如 Vue1 中在 methods 中添加的方法可以在 vue 執行個體的

$options

屬性中檢視,而 vue2 中這些方法可以直接在第一級屬性中查找或者在

$options

屬性下的原型方法中

__proto__

尋找。在 vue1 中可以檢視 vuex 這個屬性,但是 vue2 中移除了。至于其它的不同,大家可以自己對比,通過這種方式,可以深入了解 vue 的設計思想。

下圖是 Vue1 執行個體截圖:

探索 vuex 2.0 以及使用 vuejs 2.0 + vuex 2.0 建構記事本應用

ES5 實作擴充運算符

假設其它元件都以這種方式改好了,就在我們滿心歡喜地運作示例時,又報錯了。問題出在擴充運算符

...

上,webpack-simple 這個模闆無法解析 ES6 的

...

。為此,我又折騰了很久,想試着修改 webpack  的配置檔案,但改動太大。我妥協了,決定抛棄擴充運算符,手寫這個方法。當然如果使用 webpack 的模闆就沒有問題,這個比較簡單,我們最後再說。

手寫擴充運算符

...

之前,我們先看一下

mapActions

這個方法。對于

mapGetters

mapActions

這兩個函數,最簡單的了解辦法就是檢視 vuex 的源碼,最終傳回的是一個對象。也就是根據需要擷取

store.js

actions

對象的某些方法。然後通過擴充運算符把傳回的對象拆開然後挂到 Vue 執行個體上。舉例來說(以下隻是擴充運算符的用法之一,别的用法可以參考其它的文章):

var obj = {         a: 1,         b: 2,     }     var methods = {         ...obj     }     // console.log(methods)     {         a: 1,         b: 2     }      

明白擴充運算符的用法之後就好辦了。為了簡單一點,我直接使用 babel 官網的線上解析器,檢視擴充運算符的 ES5 寫法。

// ES5 實作擴充運算符...     var _extends = Object.assign || function(target) {         for (var i = 1; i < arguments.length; i++) {             var source = arguments[i];             for (var key in source) {                 if (Object.prototype.hasOwnProperty.call(source, key)) {                      target[key] = source[key];                  }              }          }         return target;      };      

完整的

Toolbar.vue

元件代碼如下:

<template>       <div id="toolbar">         <i @click="addNote" class="glyphicon glyphicon-plus"></i>         <i @click="toggleFavorite"           class="glyphicon glyphicon-star"           :class="{starred: activeNote.favorite}"></i>         <i @click="deleteNote" class="glyphicon glyphicon-remove"></i>         <i @click="_test" class="glyphicon glyphicon-remove"></i>       </div>     </template>     <script>     import { mapGetters, mapActions } from 'vuex'     // ES5 實作擴充運算符...     var _extends = Object.assign || function(target) {         for (var i = 1; i < arguments.length; i++) {             var source = arguments[i];             for (var key in source) {                 if (Object.prototype.hasOwnProperty.call(source, key)) {                      target[key] = source[key];                  }              }          }         return target;      };     var actions = mapActions([       'addNote',       'deleteNote',       'toggleFavorite'     ]);     var methodsObj = _extends({},actions)     export default {       computed: mapGetters([         'activeNote'       ]),       methods:methodsObj     }     </script>      

其餘兩個子元件類似,相信大家已經明白了我的思路,具體代碼如下:

NotesList.vue

<template>       <div id="notes-list">         <div id="list-header">           <h2>Notes | coligo</h2>           <div class="btn-group btn-group-justified" role="group">             <!-- All Notes button -->             <div class="btn-group" role="group">               <button type="button" class="btn btn-default"                 @click="show = 'all'"                 :class="{active: show === 'all'}">                 All Notes               </button>             </div>             <!-- Favorites Button -->             <div class="btn-group" role="group">               <button type="button" class="btn btn-default"                 @click="show = 'favorites'"                 :class="{active: show === 'favorites'}">                 Favorites               </button>             </div>           </div>         </div>         <!-- render notes in a list -->         <div class="container">           <div class="list-group">             <a v-for="note in filteredNotes"               class="list-group-item" href="#"               :class="{active: activeNote === note}"               @click="updateActiveNote(note)">               <h4 class="list-group-item-heading">                 {{note.text.trim().substring(0, 30)}}               </h4>             </a>           </div>         </div>       </div>     </template>     <script>     import { mapGetters, mapActions } from 'vuex'     // ES5 實作擴充運算符...     var _extends = Object.assign || function(target) {         for (var i = 1; i < arguments.length; i++) {             var source = arguments[i];             for (var key in source) {                 if (Object.prototype.hasOwnProperty.call(source, key)) {                      target[key] = source[key];                  }              }          }         return target;      };     var getters = mapGetters([       'activeNote'     ]);     var filters = {       filteredNotes: function () {         if (this.show === 'all'){           return this.$store.state.notes         } else if (this.show === 'favorites') {           return this.$store.state.notes.filter(note => note.favorite)         }       }     }     var actions = mapActions(['updateActiveNote'])     var computedObj = _extends({},getters,filters);     var methodsObj = _extends({},actions);     export default {       data () {         return {           show: 'all'         }       },       computed:computedObj,       methods:methodsObj     }     </script>      

Editor.vue

<template>       <div id="note-editor">         <textarea           :value="activeNoteText"           @input="editNote"           class="form-control">         </textarea>       </div>     </template>     <script>     import { mapGetters, mapActions } from 'vuex'     export default {       computed:mapGetters(['activeNoteText']),       methods:mapActions(['editNote'])     }     </script>      

Webpack 模闆

直接使用 vue-cli 的 webpack 模闆就會簡單很多,可以直接解析擴充運算符,代碼也會比較簡潔。我就不多說了,直接貼上 github 的位址,大家有不懂的可以看一下:https://github.com/nzbin/notes-app-vuejs2-vuex2

總結

終于寫完了這篇文章,感慨頗多。這個例子比較典型,學習的人很多,可能我并不是第一個重寫這個案例的人,我隻是與大家分享我的一些心得。順便提一句,為了重寫這個示例并解決遇到的這些小問題,我們可能要使用很多資源,比如 github、codePen、stackoverflow、npm 官網、babel 官網、vuejs 官網、vuex 官網、部落格等等。回頭再想想 Vue 到底是什麼,一個對象,沒錯,一個集合了很多屬性和方法的對象。為什麼要強調面向對象的重要性,可能這就是最好的闡釋,包括 jQuery、react、其它架構等等。一旦遇到問題,在控制台列印 Vue 執行個體,反複檢視其屬性可能很有幫助。

最後發個預告,下一篇文章我想探讨一下面向對象的 CSS,分析幾個優秀的 UI 架構,我相信每個人都可以書寫屬于自己的 CSS 架構。

感謝您的閱讀,如果您對我的文章感興趣,可以關注我的部落格,我是叙帝利,下篇文章再見!

開發低代碼平台的必備拖拽庫 https://github.com/ng-dnd/ng-dnd

基于 Angular Material 的中背景管理架構 https://github.com/ng-matero/ng-matero

Angular Material Extensions 擴充元件庫 https://github.com/ng-matero/extensions

仿 Windows 照片檢視器插件 https://github.com/nzbin/photoviewer

仿 Windows 照片檢視器插件 jQuery 版 https://github.com/nzbin/magnify

完美替代 jQuery 的子產品化 DOM 庫 https://github.com/nzbin/domq

簡化類名的輕量級 CSS 架構 https://github.com/nzbin/snack

與任意 UI 架構搭配使用的通用輔助類 https://github.com/nzbin/snack-helper

單元素純 CSS 加載動畫 https://github.com/nzbin/three-dots

有趣的 jQuery 卡片抽獎插件 https://github.com/nzbin/CardShow

懸疑科幻電影推薦 https://github.com/nzbin/movie-gallery

鍛煉記憶力的小程式 https://github.com/nzbin/memory-stake