阅读 403

vuex入门小项目

在这篇教程中我们将通过构建一个笔记应用来学习如何在我们的 Vue 项目中使用 Vuex。这个例子是基于vue2.0和vuex3.0的版本,我们将大概的过一遍什么是 Vuex.js,在项目中什么时候使用它,和如何构建我们的 Vue 应用。因为是新手,有什么错误,请大家指出,谢谢。

项目源码:vuex-notes-app;有需要的同学可以直接下载源码查看。

vuex简介

在Vue中,多组件的开发给我们带来了很多的方便,但同时当项目规模变大的时候,多个组件间的数据通信和状态管理就显得难以维护。而Vuex就此应运而生。将状态管理单独拎出来,应用统一的方式进行处理,在后期维护的过程中数据的修改和维护就变得简单而清晰了。Vuex采用和Redux类似的单向数据流的方式来管理数据。用户界面负责触发动作(Action)进而改变对应状态(State),从而反映到视图(View)上。如下图所示:

下面这张图详细的解释了 Vuex 应用中数据的流向(Vuex 官方图)简单解释下:


Vuex 规定,属于应用层级的状态只能通过 Mutation 中的方法来修改,而派发 Mutation 中的事件只能通过 action。

从左到又,从组件出发,组件中调用 action,在 action 这一层级我们可以和后台数据交互,比如获取初始化的数据源,或者中间数据的过滤等。然后在 action 中去派发 Mutation。Mutation 去触发状态的改变,状态的改变,将触发视图的更新。

注意事项

  • 数据流都是单向的
  • 组件能够调用 action
  • action 用来派发 Mutation
  • 只有 mutation 可以改变状态
  • store 是响应式的,无论 state 什么时候更新,组件都将同步更新


按我个人来理解,vuex的目的就是为了解决vue中组件和组件间传值的问题,就是各个组件中同享同样的数据,组件之间可以通过vuex来修改其他组件的数据,因为他们之间的数据是同享的,牵一发而动全身。

Vuex 的核心内容主要就是 State、Getters、Mutations、Actions 这四部分,可以这样理解:

State就是大家共同拥有数据,

Getters将state数据进行各种操作(筛选,过滤)并输出,

Mutations可以执行一些操作修改state数据,只能是同步的,

Actions:和Mutations差不多,它可以是异步操作,提交mutaions改变状态。

辅助函数与上面对应的是mapState、mapGetters、mapMutations、mapActions,

mapState、mapGetters通过Vue的Computed获得Vuex的state和getters

mapMutations、mapActions则通过methods来获取对应的mutations和actions

环境安装

这个应用将使用 webpack 来做模块打包,使用 Vue 官方提供的脚手架 vue-cli

安装 vue-cli

npm install -g vue-cli复制代码

初始化应用

vue init webpack chen-vue-notes复制代码
cd chen-vue-notes复制代码
npm install // 安装依赖包复制代码
npm run dev // 启动服务复制代码

项目的结构大概如下图:


项目组件划分

在这个项目中,我们将总共使用四个组件:根组件 App.vue,操作栏组件 Toolbar.vue,别表组件 NotesList.vue,笔记编辑组件 Editor.vue。


创建 Vuex Store

按照上面我们列出来的功能模块,我们在 store下面建立一个 store.js 文件

import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions.js';
import getters from './getters.js';

Vue.use(Vuex);

// 需要维护的状态
const state = {
  notes: [],
  activeNote: {},
  show: ''
};
const mutations = {
  INIT_STORE(state, data) {
    console.log(data.activeNote)
    state.notes = data.notes;
    state.show = data.show;
    state.activeNote = data.activeNote;
  },
  // 新增笔记
  NEW_NOTE(state) {
    var newNote = {
      id: +new Date(),
      title: '笔记标题',
      content: '内容',
      favorite: false
    };
    state.notes.push(newNote);
    state.activeNote = newNote;
  },
  // 修改笔记
  EDIT_NOTE(state, note) {
    state.activeNote = note;
    // 修改原始数据
    for (var i = 0; i < state.notes.length; i++) {
      if(state.notes[i].id === note.id){
        state.notes[i] = note;
        break;
      }
    };
  },
  // 删除笔记
  DELETE_NOTE(state) {
    let note=state.activeNote;
    var a;
    console.log(note.id)
    for (var i = 0; i < state.notes.length; i++) {
      if(state.notes[i].id === note.id){
       a=i;
      }
    };
    console.log(a)
    state.notes.splice(a,1);
    state.activeNote = state.notes[0] || {};
  },
  // 切换笔记的收藏与取消收藏
  TOGGLE_FAVORITE(state) {
    state.activeNote.favorite = !state.activeNote.favorite;
  },
  // 切换显示数据列表类型:全部 or 收藏
  SET_SHOW_ALL(state, show){
    state.show = show;
    // 切换数据展示,需要同步更新 activeNote
    if(show === 'favorite'){
      state.activeNote = state.notes.filter(note => note.favorite)[0] || {};
    }else{
      state.activeNote = state.notes[0] || {};
    }
  },
  // 设置当前激活的笔记
  SET_ACTIVE_NOTE(state, note) {
    state.activeNote = note;
  }
}
export
default new Vuex.Store({
  state,
  actions,
  mutations,
  getters
});
复制代码

创建 Vuex Actions

在 store 下面建立一个 action.js,用来给组件使用的函数

function makeAction(type) {
  return ({commit},...args)=>commit(type,...args)
}
const initNote = {
  id:
+new Date(),
  title: '我的笔记',
  content: '第一篇笔记内容',
  favorite: false
};
// 模拟初始化数据
const initData = {
  show: 'all',
  notes: [initNote],
  activeNote: initNote
};

export default{
  //初始化数据
  initStore: ({commit})=>{
    commit('INIT_STORE', initData);
  },
  updateActiveNote: makeAction('SET_ACTIVE_NOTE'),
  newNote: makeAction('NEW_NOTE'),
  deleteNote: makeAction('DELETE_NOTE'),
  toggleFavorite: makeAction('TOGGLE_FAVORITE'),
  editNote: makeAction('EDIT_NOTE'),
  updateShow: makeAction('SET_SHOW_ALL'),
}复制代码

创建 Vuex getters

建立一个 getter.js 文件,用来从 store 获取数据

export default {
  filteredNotes: (state) => {
    if(state.show === 'all'){
      return state.notes || {};
    }else if(state.show === 'favorite'){
      return state.notes.filter(note => note.favorite) || {};
    }
  },
  show: (state) => {
    return state.show;
  },
  activeNote: (state) => {
    console.log(state.activeNote)
    return state.activeNote;
  }
}复制代码

路由配置

在这里我们将使用 vue-router 来做路由,引用 bootstrap 样式。

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>chen-vuex-notes</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected
-->
  </body>
</html>复制代码

所有的入口逻辑我们都将在 main.js 中编写

main.js

import Vue from 'vue'
import App from './App'
import VueRouter from 'vue-router';
import store from './store/store';
Vue.config.productionTip
= false
Vue.use(VueRouter);
const routes = [
  { path: '/index', component: App },
  { path: '/', redirect: '/index'}
]
const router = new VueRouter({
  routes
});
/* eslint-disable
no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})
复制代码

根组件 App.vue

<template>
  <div id="app" class="app">
    <Toolbar></Toolbar>
    <NotesList></NotesList>
    <Editor></Editor>
  </div>
</template>

<script>
  import Toolbar from './components/Toolbar';
  import NotesList from './components/NotesList';
  import Editor from './components/Editor';
  import {mapActions} from 'vuex'
  export default {
    components:{
      Toolbar,
      NotesList,
      Editor
    },
    methods:{
      ...mapActions(['initStore']),
    },
    mounted(){
      console.log(111)
      this.initStore()
    }
  }
</script>

<style>
  html, #app {
    height: 100%;
  }

  body {
    margin: 0;
    padding: 0;
    border: 0;
    height: 100%;
    max-height: 100%;
    position: relative;
  }
</style>复制代码

Toolbar.vue

在这里,我们用到了 Vuex 的一个案例就是我们需要知道当前的激活的笔记是否是收藏类别的,如果是,我们需要高亮收藏按钮,那么如何知道呢?那就是通过 vuex 里面的 getters 获取当前激活的笔记对象,判断它的 favorite 是否为 true。

始终牢记一个概念,vuex 中数据是单向的,只能从 store 获取,而我们这个例子中的 activeNote 也是始终都在 store.js 中维护的,这样子就可以给其他组件公用了

<template>
  <div id="toolbar">
    <i class="glyphicon logo"><img src="../assets/logo.png" width="30" height="30"></i>
    <i @click="newNote" 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 {mapActions, mapGetters} from 'vuex'
export default {
  computed:mapGetters(['activeNote']),
  methods:mapActions([
    'newNote',
    'deleteNote',
    'toggleFavorite'
  ])
}
</script>

<style lang="scss" scoped>
  #toolbar{
    float: left;
    width: 80px;
    height: 100%;
    background-color: #30414D;
    color: #767676;
    padding: 35px 25px 25px 25px;

  .starred {
    color: #F7AE4F;
  }

  i{
    font-size: 30px;
    margin-bottom: 35px;
    cursor: pointer;
    opacity: 0.8;
    transition: opacity 0.5s ease;

  &:hover{
     opacity: 1;
   }
  }
  }
</style>复制代码


NotesList.vue

笔记列表组件,主要有三个操作

  • 渲染笔记
  • 切换渲染笔记
  • 点击列表 title,切换 activeNote

我们通过 getters 中的 filteredNotes 方法获取笔记列表

<template>
  <div id="notes-list">
    <div id="list-header">
      <h2>Notes | heavenru.com</h2>
      <div class="btn-group btn-group-justified" role="group">
        <!-- all -->
        <div class="btn-group" role="group">
          <button type="button" class="btn btn-default"
                  @click="toggleShow('all')"
                  :class="{active: show === 'all'}">All Notes</button>
        </div>
        <!-- favorites -->
        <div class="btn-group" role="group">
          <button type="button" class="btn btn-default"
                  @click="toggleShow('favorite')"
                  :class="{active: show === 'favorite'}">Favorites</button>
        </div>
      </div>
    </div>

    <!-- 渲染笔记列表
-->
    <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.title.trim().substring(0,30)}}
          </h4>
        </a>
      </div>
    </div>
  </div>
</template>

<script>
  import {mapGetters,mapActions} from 'vuex'

  export default {
    computed:mapGetters([
     'filteredNotes',
     'show',
     'activeNote',
    ]),
    methods:{
      ...mapActions(['updateActiveNote','updateShow']),
      toggleShow(show) {
        console.log(show)
        this.updateShow(show);
      },
    }
  }
</script>

<style lang="scss" scoped>
  #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;

      h2{
        font-weight: 300;
        text-transform: uppercase;
        text-align: center;
        font-size: 22px;
        padding-bottom: 8px;
      }

      .search{
        margin-top: 20px;
      }
    }

    .container{
      height: calc(100% - 204px);
      max-height: calc(100% - 204px);
      overflow: auto;
      width: 100%;
      padding: 0;

      .list-group-item{
        border: 0;
        border-radius: 0;

        .list-group-item-heading{
          font-weight: 300;
          font-size: 15px;
        }
      }
    }
  }
</style>复制代码

Editor.vue

在 Editor.vue 组件中,我们需要能够实时的更新当前的 activeNote 组件和列表中对应的我们正在修改的笔记对象的内容。

<template>
  <div id="note-editor">
    <div class="form-group">
      <input type="text" name="title"
             class="title form-control"
             placeholder="请输入标题"
             @input="updateNote"
             v-model="currentNote.title">
      <textarea
        v-model="currentNote.content" name="content"
        class="form-control" row="3" placeholder="请输入正文"
        @input="updateNote"></textarea>
    </div>
  </div>
</template>
<script>
  import {mapGetters,mapActions} from 'vuex'

  export default {
    computed: {
      ...mapGetters(['activeNote']),
      currentNote(){
        return this.activeNote
      }
    },
    methods:{
      ...mapActions(['editNote']),
      updateNote() {
        this.editNote(this.currentNote);
      },
    }
  }
</script>
<style lang="scss" scoped>
  #note-editor {
    height: 100%;
    margin-left: 380px;

  .form-group{
    height: 50%;
  }
  .title{
    border:0;
  }
  textarea{
    height: 100%;
    border: 0;
    border-radius: 0;
    padding-top: 15px;
  }
  }
</style>复制代码


文章分类
前端