探索 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 框架。

相关文章
相关标签/搜索