詳解探索 vuex 2.0 以及使用 vuejs 2.0 + vuex 2.0 構(gòu)建記事本應(yīng)用
前言
首先說(shuō)明這并不是一個(gè)教程貼,而記事本應(yīng)用是網(wǎng)上早有的案例,對(duì)于學(xué)習(xí) vuex 非常有幫助。我的目的是探索 vuex 2.0 ,然后使用 vuejs 2.0 + vuex 2.0 重寫(xiě)這個(gè)應(yīng)用,其中最大的問(wèn)題是使用 vue-cli 構(gòu)建應(yīng)用時(shí)遇到的問(wèn)題。通過(guò)這些問(wèn)題深入探索 vue 以及 vuex 。
我對(duì)于框架的學(xué)習(xí)一直斷斷續(xù)續(xù),最先接觸的是 react,所以有一些先入為主的觀念,喜歡 react 更多一點(diǎn),尤其在應(yīng)用的構(gòu)建層面來(lái)說(shuō)。之所以斷斷續(xù)續(xù),是因?yàn)樽约?JS 基礎(chǔ)較弱,剛開(kāi)始學(xué)習(xí)的時(shí)候,只是比著葫蘆畫(huà)瓢,雖然可以做出點(diǎn)東西,但對(duì)于其中的一些概念仍然云里霧里,不知所云,無(wú)法深入理解框架。所以我又臨時(shí)放棄框架的學(xué)習(xí),開(kāi)始學(xué)習(xí) JS 基礎(chǔ)。事實(shí)證明打牢基礎(chǔ)之后,學(xué)習(xí)框架以及理解框架是神速的。而學(xué)習(xí) webgl 和 three.js 的過(guò)程與此類(lèi)似。沒(méi)有 webgl 的基礎(chǔ),學(xué)習(xí) three.js 只會(huì)停留在初級(jí)階段。
我在過(guò)去的半年參加了很多面試,幾乎無(wú)一例外的都會(huì)被問(wèn)框架的使用情況,但是其中很多公司屬于隨波逐流,使用框架比較盲目。甚至覺(jué)得使用框架是極其高大上的事情。雖然我學(xué)習(xí)過(guò)框架,但畢竟沒(méi)有深入學(xué)習(xí)也沒(méi)有拿得出手的項(xiàng)目,所以只是只言片語(yǔ)的說(shuō)兩句,大部分知識(shí)是懵懂的。然而面對(duì)面試官不屑的神情以及以此作為選拔的指標(biāo),心想這樣的面試官太膚淺。當(dāng)然很多公司的面試還是以基礎(chǔ)為主??蚣軐儆谔剿鳎ハ鄬W(xué)習(xí)的狀態(tài)。我在這篇文章中強(qiáng)調(diào)一點(diǎn),學(xué)習(xí)能力以及解決問(wèn)題的能力更重要。
開(kāi)始吧
言歸正傳,對(duì)于這個(gè)筆記本案例,大家可以直接百度搜 vue notes ,這是一篇英文教程,大家看到的都是翻譯的。在剛開(kāi)始 vue 資料稀缺的時(shí)候,這樣的文章非常珍貴。demo 點(diǎn)這里。說(shuō)白了,算是 todoMVC 案例的一個(gè)變體。當(dāng)初覺(jué)得這個(gè)例子非常好,想跟著學(xué)一學(xué),結(jié)果一拖半年過(guò)去了。這幾天終于抽時(shí)間把這個(gè)例子敲了一遍。學(xué)習(xí)在于舉一反三。如果大家按照網(wǎng)上教程來(lái)做,那么 NPM 包默認(rèn)安裝的都是最新版本,運(yùn)行會(huì)報(bào)錯(cuò)。所以如果用 vuex 2 要怎么寫(xiě)呢?
以下是 notes-vuex-app 的源文件目錄:

在使用 vue 2 重寫(xiě)這個(gè) app 之前,我在想能不能不改變文件目錄結(jié)構(gòu)以及配置位置呢?就是用比較生硬的方式重寫(xiě),或者說(shuō)單純的語(yǔ)法修改。事實(shí)是可行的,否則我就不會(huì)寫(xiě)這篇文章了。然而面對(duì)的問(wèn)題非常多,但卻因此深入的理解了 vue 以及 vuex。最大的問(wèn)題是 webpack 的構(gòu)建,如果使用 webpack 2.0+的話,坑比較多。本人是菜鳥(niǎo),所以最終選擇了 vue-cli 提供的兩個(gè) webpack 的模板,分別是 webpack-simple 和 webpack,我先使用 webpack-simple,它和原 app 的結(jié)構(gòu)基本吻合。目錄如下:

使用 vue-cli 生成基本目錄之后,再安裝 vuex2 。
main.js 的小改動(dòng)
原示例 main.js 如下所示,但運(yùn)行出錯(cuò)了,主要是 Vue 2 的根實(shí)例渲染稍有變化
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 的變化
這個(gè)應(yīng)用改寫(xiě)的主要問(wèn)題集中在 vuex 2 的變化上,這些變化確實(shí)會(huì)讓人感到凌亂,我無(wú)數(shù)次抓耳撓腮的罵娘。不過(guò)通過(guò)官方給出的示例也可以看出一些端倪。
首先是 action.js,只需注意一點(diǎn),所有的 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 變化也不大,但是要注意幾個(gè)地方:
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
})
原示例文件中沒(méi)有將 getters 寫(xiě)到 store.js 中,而是直接在組件中定義的。為了更清晰,我仿照官方示例也提取出來(lái)寫(xiě)在了 store.js 中,這樣在組件中調(diào)用時(shí)比較方便。其次也引入了 action.js,并作為 actions 對(duì)象傳遞給 Vuex.store(),這算是 vuex 的標(biāo)準(zhǔn)寫(xiě)法吧,對(duì)于后面在組件中調(diào)用比較有利。
其中要注意 DELETE_NOTE (state){} 這個(gè)方法,原示例使用了 vue1 提供的 remove 方法,但是 vue2 中去掉了這個(gè)方法。仔細(xì)想想就會(huì)明白,這個(gè)函數(shù)的作用就是刪除 notes 數(shù)組中的元素??梢允褂迷?splice 方法。如果 JS 基礎(chǔ)扎實(shí)的話,這里應(yīng)該很好理解,沒(méi)有什么大問(wèn)題。其次相比原示例,添加一個(gè)刪除后操作的判斷。
我之前一直不太理解 flux 的概念,感覺(jué)像是新東西,完全不知道它的目的及作用。換成 Vuex,還是有點(diǎn)稀里糊涂。但是通過(guò)修改這個(gè)示例,基本算是開(kāi)竅了。這些東西本身并沒(méi)有玄機(jī)奧妙,想一想,如果我們不用框架,而是自己手寫(xiě)一個(gè) todoMVC 時(shí)要怎么做?應(yīng)該也是這樣的思路,定義一個(gè) notes 數(shù)組變量以及 activeNote 的變量。然后在創(chuàng)建一些改變狀態(tài)的方法。我在面試中遇到過(guò)一個(gè)情況,面試官反復(fù)問(wèn)我為什么需要使用框架,用 jQuery 不是也可以實(shí)現(xiàn)嗎?這樣說(shuō)確實(shí)沒(méi)錯(cuò),用比較原始的方法當(dāng)然可以做,只是代碼結(jié)構(gòu)會(huì)冗余或者凌亂,缺少小而美的特點(diǎn)??蚣芤约霸O(shè)計(jì)模式對(duì)代碼做了整合封裝,對(duì)于一個(gè) CURD 應(yīng)用比較友好,實(shí)現(xiàn)起來(lái)更方便更簡(jiǎn)單。我對(duì)于 Vuex 的理解就是,它是一個(gè)對(duì)象,封裝了與狀態(tài)相關(guān)的方法和屬性。而所謂的狀態(tài)就是點(diǎn)擊、按鍵等操作之后的變化。
組件中使用 vuex
先看一下 Toolbar.vue 這個(gè)組件。修改后的代碼如下:
<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>
通過(guò)和原示例代碼對(duì)比,這里的區(qū)別一目了然。我通過(guò)在控制臺(tái)打印 Vue 實(shí)例,折騰很長(zhǎng)時(shí)間才大體明白怎么回事。vuex 1 在組件中使用時(shí)會(huì)直接將 getters 以及 actions 掛到 vuex 這個(gè)屬性上,沒(méi)有提供 mapGetters 及 mapActions 等一些方法。而 vuex2 使用 mapGetters 及 mapActions 等一些方法將 actions 的方法掛到 Vue 實(shí)例上??偟膩?lái)說(shuō),都是把 actions 的方法掛到 Vue 實(shí)例上。我們從這個(gè)層面上談?wù)?Vue 實(shí)例,Vue 2 的變化就是其屬性的變化。比如 Vue1 中在 methods 中添加的方法可以在 vue 實(shí)例的 $options 屬性中查看,而 vue2 中這些方法可以直接在第一級(jí)屬性中查找或者在 $options 屬性下的原型方法中 __proto__ 尋找。在 vue1 中可以查看 vuex 這個(gè)屬性,但是 vue2 中移除了。至于其它的不同,大家可以自己對(duì)比,通過(guò)這種方式,可以深入理解 vue 的設(shè)計(jì)思想。
下圖是 Vue1 實(shí)例截圖:

ES5 實(shí)現(xiàn)擴(kuò)展運(yùn)算符
假設(shè)其它組件都以這種方式改好了,就在我們滿心歡喜地運(yùn)行示例時(shí),又報(bào)錯(cuò)了。問(wèn)題出在擴(kuò)展運(yùn)算符 ... 上,webpack-simple 這個(gè)模板無(wú)法解析 ES6 的 ...。為此,我又折騰了很久,想試著修改 webpack 的配置文件,但改動(dòng)太大。我妥協(xié)了,決定拋棄擴(kuò)展運(yùn)算符,手寫(xiě)這個(gè)方法。當(dāng)然如果使用 webpack 的模板就沒(méi)有問(wèn)題,這個(gè)比較簡(jiǎn)單,我們最后再說(shuō)。
手寫(xiě)擴(kuò)展運(yùn)算符 ... 之前,我們先看一下 mapActions 這個(gè)方法。對(duì)于 mapGetters 以及 mapActions 這兩個(gè)函數(shù),最簡(jiǎn)單的理解辦法就是查看 vuex 的源碼,最終返回的是一個(gè)對(duì)象。也就是根據(jù)需要獲取 store.js 中 actions 對(duì)象的某些方法。然后通過(guò)擴(kuò)展運(yùn)算符把返回的對(duì)象拆開(kāi)然后掛到 Vue 實(shí)例上。舉例來(lái)說(shuō)(以下只是擴(kuò)展運(yùn)算符的用法之一,別的用法可以參考其它的文章):
var obj = {
a: 1,
b: 2,
}
var methods = {
...obj
}
// console.log(methods)
{
a: 1,
b: 2
}
明白擴(kuò)展運(yùn)算符的用法之后就好辦了。為了簡(jiǎn)單一點(diǎn),我直接使用 babel 官網(wǎng)的在線解析器,查看擴(kuò)展運(yùn)算符的 ES5 寫(xiě)法。
// ES5 實(shí)現(xiàn)擴(kuò)展運(yùn)算符...
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 實(shí)現(xiàn)擴(kuò)展運(yùn)算符...
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>
其余兩個(gè)子組件類(lèi)似,相信大家已經(jīng)明白了我的思路,具體代碼如下:
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="#" rel="external nofollow"
: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 實(shí)現(xiàn)擴(kuò)展運(yùn)算符...
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 模板就會(huì)簡(jiǎn)單很多,可以直接解析擴(kuò)展運(yùn)算符,代碼也會(huì)比較簡(jiǎn)潔。我就不多說(shuō)了,直接貼上 github 的地址,大家有不懂的可以看一下:https://github.com/nzbin/notes-app-vuejs2-vuex2
總結(jié)
終于寫(xiě)完了這篇文章,感慨頗多。這個(gè)例子比較典型,學(xué)習(xí)的人很多,可能我并不是第一個(gè)重寫(xiě)這個(gè)案例的人,我只是與大家分享我的一些心得。順便提一句,為了重寫(xiě)這個(gè)示例并解決遇到的這些小問(wèn)題,我們可能要使用很多資源,比如 github、codePen、stackoverflow、npm 官網(wǎng)、babel 官網(wǎng)、vuejs 官網(wǎng)、vuex 官網(wǎng)、博客等等。回頭再想想 Vue 到底是什么,一個(gè)對(duì)象,沒(méi)錯(cuò),一個(gè)集合了很多屬性和方法的對(duì)象。為什么要強(qiáng)調(diào)面向?qū)ο蟮闹匾?,可能這就是最好的闡釋?zhuān)?jQuery、react、其它框架等等。一旦遇到問(wèn)題,在控制臺(tái)打印 Vue 實(shí)例,反復(fù)查看其屬性可能很有幫助。
最后發(fā)個(gè)預(yù)告,下一篇文章我想探討一下面向?qū)ο蟮?CSS,分析幾個(gè)優(yōu)秀的 UI 框架,我相信每個(gè)人都可以書(shū)寫(xiě)屬于自己的 CSS 框架。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vue.js在數(shù)組中插入重復(fù)數(shù)據(jù)的實(shí)現(xiàn)代碼
這篇文章主要介紹了Vue.js在數(shù)組中插入重復(fù)數(shù)據(jù)的實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-11-11
Vue報(bào)錯(cuò):Uncaught TypeError: Cannot assign to read only propert
這篇文章主要給大家介紹了關(guān)于Vue報(bào)錯(cuò):Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>' 的解決方法,文中介紹的非常詳細(xì),需要的朋友們下面來(lái)一起看看吧。2017-06-06
vue3中關(guān)于路由hash與History的設(shè)置
這篇文章主要介紹了vue3中關(guān)于路由hash與History的設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
解決vue-quill-editor上傳內(nèi)容由于圖片是base64的導(dǎo)致字符太長(zhǎng)的問(wèn)題
vue-quill-editor默認(rèn)插入圖片是直接將圖片轉(zhuǎn)為base64再放入內(nèi)容中,如果圖片較多,篇幅太長(zhǎng),就會(huì)比較煩惱,接下來(lái)通過(guò)本文給大家介紹vue-quill-editor上傳內(nèi)容由于圖片是base64的導(dǎo)致字符太長(zhǎng)的問(wèn)題及解決方法,需要的朋友可以參考下2018-08-08
vue3點(diǎn)擊出現(xiàn)彈窗后背景變暗且不可操作的實(shí)現(xiàn)代碼
這篇文章主要介紹了vue3點(diǎn)擊出現(xiàn)彈窗后背景變暗且不可操作的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08
vue的路由動(dòng)畫(huà)切換頁(yè)面無(wú)法讀取meta值的bug記錄
這篇文章主要介紹了vue的路由動(dòng)畫(huà)切換頁(yè)面無(wú)法讀取meta值的bug記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05

