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

作者: Simon王小白 | 来源:发表于2016-10-27 20:36 被阅读458次

    教程开始


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

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

    ![Paste_Image.png](http:https://img.haomeiwen.com/i1444794/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

    相关文章

      网友评论

      • 090f5007f9bb:关于 vuex,觉得最通俗易懂的一篇文章,感谢作者
      • 36a1fa5ab712:请教个问题,假如在收藏列表选中一个笔记本,然后点击取消收藏,要求当前选中的笔记本消失
        这个功能如何实现?
        Simon王小白:@36a1fa5ab712 目前demo中,选中收藏列表中的一个笔记本,然后点击取消收藏,在收藏列表中该笔记本会消失。因为在activeNote对象中,有放入笔记本对象favorite,根据笔记本对象中参数favorite来判断是否对改笔记本进行收藏并显示。demo地址:https://github.com/wzxm/vue-webpack.git
      • 古斯比德:不错不错,好文!
        Simon王小白:@古斯比德 谢谢支持。
      • 47be92e8d24b:简明易懂
        47be92e8d24b: @Simon王小白 最近研究明白了,谢谢啦
        Simon王小白:谢谢支持

      本文标题:Vuex 进阶,创建一个笔记本项目

      本文链接:https://www.haomeiwen.com/subject/gxjsuttx.html