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

前言

首先說明這並非一個教程貼,而記事本應用是網上早有的案例,對於學習 vuex 很是有幫助。個人目的是探索 vuex 2.0 ,而後使用 vuejs 2.0 + vuex 2.0 重寫這個應用,其中最大的問題是使用 vue-cli 構建應用時遇到的問題。經過這些問題深刻探索 vue 以及 vuexvue

我對於框架的學習一直斷斷續續,最早接觸的是 react,因此有一些先入爲主的觀念,喜歡 react 更多一點,尤爲在應用的構建層面來講。之因此斷斷續續,是由於本身 JS 基礎較弱,剛開始學習的時候,只是比着葫蘆畫瓢,雖然能夠作出點東西,但對於其中的一些概念仍然雲裏霧裏,不知所云,沒法深刻理解框架。因此我又臨時放棄框架的學習,開始學習 JS 基礎。事實證實打牢基礎以後,學習框架以及理解框架是神速的。而學習 webgl 和 three.js 的過程與此相似。沒有 webgl 的基礎,學習 three.js 只會停留在初級階段。react

我在過去的半年參加了不少面試,幾乎無一例外的都會被問框架的使用狀況,可是其中不少公司屬於隨波逐流,使用框架比較盲目。甚至以爲使用框架是極其高大上的事情。雖然我學習過框架,但畢竟沒有深刻學習也沒有拿得出手的項目,因此只是隻言片語的說兩句,大部分知識是懵懂的。然而面對面試官不屑的神情以及以此做爲選拔的指標,心想這樣的面試官太膚淺。固然不少公司的面試仍是以基礎爲主。框架屬於探索,互相學習的狀態。我在這篇文章中強調一點,學習能力以及解決問題的能力更重要。webpack

開始吧

言歸正傳,對於這個筆記本案例,你們能夠直接百度搜 vue notes ,這是一篇英文教程,你們看到的都是翻譯的。在剛開始 vue 資料稀缺的時候,這樣的文章很是珍貴。demo 點這裏。說白了,算是 todoMVC 案例的一個變體。當初以爲這個例子很是好,想跟着學一學,結果一拖半年過去了。這幾天終於抽時間把這個例子敲了一遍。學習在於觸類旁通。若是你們按照網上教程來作,那麼 NPM 包默認安裝的都是最新版本,運行會報錯。因此若是用 vuex 2 要怎麼寫呢?git

如下是 notes-vuex-app 的源文件目錄:github

 

在使用 vue 2 重寫這個 app 以前,我在想能不能不改變文件目錄結構以及配置位置呢?就是用比較生硬的方式重寫,或者說單純的語法修改。事實是可行的,不然我就不會寫這篇文章了。然而面對的問題很是多,但卻所以深刻的理解了 vue 以及 vuex。最大的問題是 webpack 的構建,若是使用 webpack 2.0+的話,坑比較多。本人是菜鳥,因此最終選擇了 vue-cli 提供的兩個 webpack 的模板,分別是 webpack-simplewebpack,我先使用 webpack-simple,它和原 app 的結構基本吻合。目錄以下:web

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

main.js 的小改動

原示例 main.js 以下所示,但運行出錯了,主要是 Vue 2 的根實例渲染稍有變化vuex

import Vue from 'vue'
import store from './vuex/store'
import App from './components/App.vue'

new Vue({
  store, // 注入到全部子組件
  el: 'body',
  components: { App }
})

改正以後:vue-cli

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 }
})

或者npm

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 這個屬性上,沒有提供 mapGettersmapActions 等一些方法。而 vuex2 使用 mapGettersmapActions 等一些方法將 actions 的方法掛到 Vue 實例上。總的來講,都是把 actions 的方法掛到 Vue 實例上。咱們從這個層面上談談 Vue 實例,Vue 2 的變化就是其屬性的變化。好比 Vue1 中在 methods 中添加的方法能夠在 vue 實例的 $options 屬性中查看,而 vue2 中這些方法能夠直接在第一級屬性中查找或者在 $options 屬性下的原型方法中 __proto__ 尋找。在 vue1 中能夠查看 vuex 這個屬性,可是 vue2 中移除了。至於其它的不一樣,你們能夠本身對比,經過這種方式,能夠深刻理解 vue 的設計思想。

下圖是 Vue1 實例截圖:

ES5 實現擴展運算符

假設其它組件都以這種方式改好了,就在咱們滿心歡喜地運行示例時,又報錯了。問題出在擴展運算符 ... 上,webpack-simple 這個模板沒法解析 ES6 的 ...。爲此,我又折騰了好久,想試着修改 webpack  的配置文件,但改動太大。我妥協了,決定拋棄擴展運算符,手寫這個方法。固然若是使用 webpack 的模板就沒有問題,這個比較簡單,咱們最後再說。

手寫擴展運算符 ... 以前,咱們先看一下 mapActions 這個方法。對於 mapGetters 以及 mapActions 這兩個函數,最簡單的理解辦法就是查看 vuex 的源碼,最終返回的是一個對象。也就是根據須要獲取 store.jsactions 對象的某些方法。而後經過擴展運算符把返回的對象拆開而後掛到 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 框架。

相關文章
相關標籤/搜索