Vuex 进阶,创建一个笔记本项目

教程开始


让我们通过这个例子,来理解 Vuex2.0 给我们带来的便利,之前在研究 Vuex2.0 的时候也是踩了一些坑,所以写了本文,也是为了减少后面出现更多的学习者避免踩坑。

我们会通过这个例子解释相应的概念,以及 Vuex 所要解决的问题:如何管理一个包含许多组件的大型应用。我们假定这个例子使用以下四个组件:最外层(App.vue),左边(toolbar),中间(noteslist),右边(editor)

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1444794-d346609162594f9b.png?
imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

** App.vue **
app 组件,它包含了另外三个子组件:

  • toolbar
  • noteslist
  • editor
<template>
  <div id="app">
    <toolbar></toolbar>
    <notes-list></notes-list>
    <editor></editor>
  </div>
</template>

<script>
import Toolbar from './components/toolbar'
import NotesList from './components/noteslist'
import Editor from './components/editor'

export default {
  components: {
    Toolbar, NotesList, Editor
  }
}
</script>
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Raleway:400,300);
html, #app {
  height: 100%;
}
body {
  margin: 0;
  padding: 0;
  border: 0;
  height: 100%;
  max-height: 100%;
  position: relative;
}
#toolbar {
  float: left;
  width: 80px;
  height: 100%;
  background-color: #30414D;
  color: #767676;
  padding: 35px 25px 25px 25px;
}
#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;
}
#note-editor {
  height: 100%;
  margin-left: 380px;
}
#note-editor textarea {
  height: 100%;
  border: 0;
  border-radius: 0;
}
#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>

** component/toolbar.vue **

<template>
  <div id="toolbar">
    <i class="glyphicon glyphicon-plus"></i>
    <i class="glyphicon glyphicon-star"></i>
    <i class="glyphicon glyphicon-remove"></i>
  </div>
</template>
<script>
export default {}
</script>

** component/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">
            All Notes
          </button>
        </div>
        <!-- Favorites Button -->
        <div class="btn-group" role="group">
          <button type="button" class="btn btn-default">
            Favorites
          </button>
        </div>
      </div>
    </div>
    <!-- render notes in a list -->
    <div class="container">
      <div class="list-group">
        <a class="list-group-item" href="#">
          <h4 class="list-group-item-heading">
            标题列表
          </h4>
        </a>
      </div>
    </div>
  </div>
</template>
<script>
export default {}
</script>

** component/editor.vue **

<template>
  <div id="note-editor">
    <textarea class="form-control">
    </textarea>
  </div>
</template>

<script>
export default {}
</script>
第一步:加入 store

store 存储应用所需的数据。所有组件都从 store 中读取数据,在我们开始之前,先用 npm 安装 vuex

$ npm install --save vuex

建立一个新的文件,在根目录下创建 vuex/store.js

import Vue from 'vue'
import Vuex from 'vuex'

// 使用 vuex
Vue.use(Vuex)

// 创建一个对象来保存应用启动时的初始状态
const state = {
  // TODO 放置初始状态
}

// 创建一个对象存储一系列我们接下来要写的 mutation 函数
const mutations = {
    // TODO 放置我们的状态变更函数
}

// 整合初始状态和变更函数,我们就得到了我们所需的 store
// 至此,这个 store 就可以链接到我们的应用中
export default new Vuex.Store({
  state, mutations
})

我们需要将创建的 store 让整个项目发现,所以这个时候需要修改 main.js
修改 main.js,注入 store

import Vue from 'vue'

import App from './App'
import store from './vuex/store'

new Vue({
  template: '<App/>',
  store,
  components: { App }
}).$mount('#app')
第二步:创建 action

action 是一种专门用来被 component 调用的函数,action 函数能够通过分发相应的 mutation 函数,来触发对 store 的更新。action 也可以先从 HTTP 后端或 store 中读取其他数据之后再分发更新事件。

创建一个新文件 vuex/action.js,然后写入相关函数

// 新增笔记
export const addNote = ({ commit, state }) => {
  commit('ADD_NOTE')
}

// 修改笔记
export const editNote = ({ commit, state }, e) => {
  commit('EDIT_NOTE', e.target.value)
}

// 删除笔记
export const deleteNote = ({ commit, state }) => {
  commit('DELETE_NOTE')
}

// 更新当前选中笔记
export const updateActiveNote = ({ commit, state }, note) => {
  commit('SET_ACTIVE_NOTE', note)
}

// 选中模块按钮(All note、Favorites)
export const toggleFavorite = ({ commit, state }) => {
  commit('TOGGLE_FAVORITE')
}

回顾一下我们刚刚添加的内容背后所潜藏的一些有趣的点:

  1. 我们有了一个新对象 vuex.actions,包含着新的 action;
  2. 我们没有指定特定的 store,object,state 等等。Vuex 会自动把它们串联起来;
  3. 我们可以用 this.addNote () 在任何方法中调用此 action;
  4. 我们也可以通过 @click 参数调用它,与使用其他普通的 Vue 组件方法并无二致;
  5. 我们给 action 起名叫 addNote,但是在具体使用时,我们可以根据需要进行重新命名
第三步:创建 state 和 mutation

我们在 vuex/actions.js 文件里,添加了相关的 mutation ,但是我们还没有写它所对应的具体操作,现在就让我们来将这些方法暴露出来。

修改 vuex/store.js

import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'

// 使用 vuex
Vue.use(Vuex)

// 创建一个对象来保存应用启动时的初始状态
const state = {
  // TODO 放置初始状态
  count: 0,
  notes: [],
  activeNote: []
}

// 创建一个对象存储一系列我们接下来要写的 mutation 函数
const mutations = {
  // TODO 放置我们的状态变更函数
  increment (state, amount) {
    state.count = state.count + amount
  },
  decrement (state, amount) {
    state.count = state.count - amount
  },
  DECREMENT (state, amount) {
    state.count = state.count - amount
  },
  ADD_NOTE (state) {
    console.log(state)
    const newNote = {
      text: 'New Note',
      favorite: false
    }
    state.notes.push(newNote)
    state.activeNote = newNote
  },

  EDIT_NOTE (state, text) {
    if (state.notes.length === 0) {
      const newNote = {
        text: text,
        favorite: false
      }
      state.notes.push(newNote)
      state.activeNote = newNote
      state.count++
    }
    state.activeNote.text = text
  },

  DELETE_NOTE (state) {
    let index = state.notes.indexOf(state.activeNote)
    if (index !== -1) {
      state.notes.splice(index, 1)
      state.activeNote = {}
    }
  },

  TOGGLE_FAVORITE (state) {
    state.activeNote.favorite = !state.activeNote.favorite
  },

  SET_ACTIVE_NOTE (state, note) {
    state.activeNote = note
  }
}

// 整合初始状态和变更函数,我们就得到了我们所需的 store
// 至此,这个 store 就可以链接到我们的应用中
export default new Vuex.Store({
  actions,
  getters,
  state,
  mutations
})
第四步: 创建 getter

在 store 中的数据,我们可以通过创建一个新的文件 getter 来统一获取方法,这样子不仅便于管理,有时候很多地方使用同一个方法,此时我们不需要修改一大堆页面,只需要修改 getter.js 中的方法实现就可以。

创建一个新文件 vuex/getter.js ,然后编写代码:

/**
 * 在 ES6 里你可以这样写
 * export const getCount = state => state.count
 */
export const notes = state => state.notes

export const activeNote = state => state.activeNote

export const activeNoteText = state => state.activeNote.text
第五步:在组件中获取数据,并且调用 action 方法

修改 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: 'activeNote'
  }),
  methods: mapActions([
    'addNote',
    'deleteNote',
    'toggleFavorite'
  ])
}
</script>

修改 vuex/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'

export default {
  data () {
    return {
      show: 'all'
    }
  },
  computed: {
    ...mapGetters([
      'notes', 'activeNote'
    ]),
    filteredNotes () {
      if (this.show === 'all') {
        return this.notes
      } else if (this.show === 'favorites') {
        return this.notes.filter(note => note.favorite)
      }
    }
  },
  methods: mapActions([
    'updateActiveNote'
  ])
}
</script>

修改 vuex/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>

这个时候,运行下你的程序,它可以正常工作了。

$ npm run dev

最后,总结下编写该案例时遇到的坑:

注意该案例使用的是 vue2.0 和 vuex2.0,安装插件时请不要装错;
如果使用 ES6,babel,那么请在 .babelrc 中 使用 stage-3 和 transform-object-rest-spread;

{
   "presets": ["es2015", "stage-3"],
   "plugins": ["transform-runtime", "transform-object-rest-spread"],
   "comments": false
}

附上 源码地址,欢迎大家来吐槽。githut

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容