运用 Vuex + Vue.js 构建单页运用

鉴于该篇文章浏览量大,复兴的同砚也挺多的,专程抽闲写了一篇 vue2.0 下的 vuex 运用要领,传送门:运用 Vuex + Vue.js 构建单页运用【新篇】

——————– 华美的分割线 ——————–

原文地点:https://coligo.io/learn-vuex-by-building-notes-app/

媒介:在近来进修 Vue.js 的时刻,看到外洋一篇报告了怎样运用 Vue.js 和 Vuex 来构建一个简朴笔记的单页运用的文章。觉得收成挺多,本身在它的例子的基本上进行了一些优化和自定义功用,在这里和人人分享下进修心得。

在这篇教程中我们将经由过程构建一个笔记运用来进修怎样在我们的 Vue 项目中运用 Vuex。我们将也许的过一遍什么是 Vuex.js,在项目中什么时刻运用它,和怎样构建我们的 Vue 运用。

这里放一张我们项目的预览图片:

《运用 Vuex + Vue.js 构建单页运用》

项目源码:vuex-notes-app;有须要的同砚可以直接下载源码检察。

重要知识点

  • Vuex 状况治理机制的运用
  • Vue.js 的基本 api
  • Vue-cli 脚手架的装置及运用
  • vur-router 的运用
  • ES6 的语法,这里引荐看下阮一峰的入门教程

Vuex 概述

在我们如饥似渴的最先项目之前,我们最好先花几分钟来了解下 Vuex 的中心观点。

Vuex 是一个特地为 Vue.js 运用所设想的集中式状况治理架构。它自创了 Flux 和 Redux 的设想头脑,但简化了观点,而且采用了一种为能更好发挥 Vue.js 数据相应机制而特地设想的完成。

state 如许观点首次打仗的时刻可能会觉获得有点隐约,简朴来讲就是将 state 算作我们项目中运用的数据的鸠合。然后,Vuex 使得 组件当地状况(component local state)和 运用层级状况(application state) 有了肯定的差别。

  • component local state:该状况示意仅仅在组件内部运用的状况,有点相似经由过程设置选项传入 Vue 组件内部的意义。
  • application level state:运用层级状况,示意同时被多个组件同享的状况层级。

假设有如许一个场景:我们有一个父组件,同时包括两个子组件。父组件可以很轻易的经由过程运用 props 属性来向子组件通报数据。

然则题目来了,当我们的两个子组件怎样和对方相互通讯的? 或许子组件怎样通报数据给他父组件的?在我们的项目很小的时刻,这个两个题目都不会太难,因为我们可以经由过程事宜派发和监听来完成父组件和子组件的通讯。

但是,跟着我们项目的增进:

  • 坚持对一切的事宜追踪将变得很难题。究竟哪一个事宜是哪一个组件派发的,哪一个组件该监听哪一个事宜?
  • 项目逻辑疏散在各个组件当中,很轻易致使逻辑的杂沓,不利于我们项目的保护。
  • 父组件将变得和子组件耦合愈来愈严峻,因为它须要明白的派发和监听子组件的某些事宜。

这就是 Vuex 用来处置惩罚的题目。 Vuex 的四个中心观点离别是:

  • The state tree:Vuex 运用单一状况树,用一个对象就包括了悉数的运用层级状况。至此它便作为一个『唯一数据源(SSOT)』而存在。这也意味着,每一个运用将仅仅包括一个 store 实例。单状况树让我们可以直接地定位任一特定的状况片断,在调试的过程当中也能轻易地获得全部当前运用状况的快照。
  • Getters:用来从 store 猎取 Vue 组件数据。
  • Mutators:事宜处置惩罚器用来驱动状况的变化。
  • Actions:可以给组件运用的函数,以此用来驱动事宜处置惩罚器 mutations

怎样你临时还不太明白这个四个观点,不必焦急,我们将在背面的项目实战中细致的诠释。

下面这张图细致的诠释了 Vuex 运用中数据的流向(Vuex 官方图)

《运用 Vuex + Vue.js 构建单页运用》

简朴诠释下:

Vuex 划定,属于运用层级的状况只能经由过程 Mutation 中的要领来修正,而派发 Mutation 中的事宜只能经由过程 action。

从左到又,从组件动身,组件中挪用 action,在 action 这一层级我们可以和背景数据交互,比方猎取初始化的数据源,或许中心数据的过滤等。然后在 action 中去派发 Mutation。Mutation 去触发状况的转变,状况的转变,将触发视图的更新。

注重事项

  • 数据流都是单向的
  • 组件可以挪用 action
  • action 用来派发 Mutation
  • 只要 mutation 可以转变状况
  • store 是相应式的,不管 state 什么时刻更新,组件都将同步更新

环境装置

这个运用将运用 webpack 来做模块打包,处置惩罚和热重启。运用 Vue 官方供应的脚手架 vue-cli

装置 vue-cli

npm install -g vue-cli

注:Node.js >= 4.x, 5.x 最好

初始化运用

vue init webpack vue-notes-app
cd vue-notes-app
npm install // 装置依靠包
npm run dev // 启动效劳

初始化一个项目名为vue-notes-app的运用,并挑选运用 webpack 打包体式格局。在命令行中依据提醒挑选初始化设置项。个中在挑选 JSLint 校验的时刻,引荐挑选 AirBNB 范例。

运用你最喜欢的编辑器翻开我们方才新建的项目,项目的构造也许如下图:

《运用 Vuex + Vue.js 构建单页运用》

  • components/ 文件夹用来寄存我们的 Vue 组件
  • vuex/ 文件夹寄存的是和 Vuex store 相干的东西(state object,actions,mutators)
  • build/ 文件是 webpack 的打包编译设置文件
  • config/ 文件夹寄存的是一些设置项,比方我们效劳器接见的端口设置等
  • dist/ 该文件夹一最先是不存在,在我们的项目经由 build 以后才会产出
  • App.vue 根组件,一切的子组件都将在这里被援用
  • index.html 全部项目的进口文件,将会援用我们的根组件 App.vue
  • main.js 进口文件的 js 逻辑,在 webpack 打包以后将被注入到 index.html 中

功用模块

  • 新增笔记,新增一篇笔记,编辑区显现空的笔记内容
  • 删除笔记,删除一篇笔记以后,编辑地区显现当前笔记种别的第一项
  • 笔记列表切换,分为悉数笔记和珍藏笔记两种,在切换以后,编辑地区显现当前列表的第一条笔记
  • 珍藏笔记,给当前激活的笔记打上珍藏的标签

项目组件离别

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

《运用 Vuex + Vue.js 构建单页运用》

竖立 Vuex Store

依据上面我们列出来的功用模块,我们在 Vuex/ 下面竖立一个 store.js 文件。

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

Vue.use(Vuex);

// 须要保护的状况
const state = {
  notes: [],
  activeNote: {},
  show: ''
};

const mutations = {
  // 初始化 state
  INIT_STORE(state, data) {
    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) {
    state.notes.$remove(state.activeNote);
    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,
  mutations
});

竖立 Vuex Actions

在 Vuex/ 下面竖立一个 action.js,用来给组件运用的函数。

function makeAction(type) {
  return ({ dispatch }, ...args) => dispatch(type, ...args);
};

const initNote = {
  id: +new Date(),
  title: '我的笔记',
  content: '第一篇笔记内容',
  favorite: false
};

// 模仿初始化数据
const initData = {
  show: 'all',
  notes: [initNote],
  activeNote: initNote
};

export const initStore = ({ dispatch }) => {
  dispatch('INIT_STORE', initData);
};
// 更新当前activeNote对象
export const updateActiveNote = makeAction('SET_ACTIVE_NOTE');

// 增加一个note对象
export const newNote = makeAction('NEW_NOTE');

// 删除一个note对象
export const deleteNote = makeAction('DELETE_NOTE');
export const toggleFavorite = makeAction('TOGGLE_FAVORITE');
export const editNote = makeAction('EDIT_NOTE');

// 更新列表展现
export const updateShow = makeAction('SET_SHOW_ALL');

竖立 Vuex Getters

在 vuex/ 下面竖立一个 getter.js 文件,用来从 store 猎取数据。

// 猎取 noteList,这里将会依据 state.show 的状况做数据过滤
export const filteredNotes = (state) => {
  if(state.show === 'all'){
    return state.notes || {};
  }else if(state.show === 'favorite'){
    return state.notes.filter(note => note.favorite) || {};
  }
};


// 猎取列表展现状况 : all or favorite
export const show = (state) => {
  return state.show;
};

// 猎取当前激活 note
export const activeNote = (state) => {
  return state.activeNote;
};

以上就是我们 Vuex 的一切逻辑了,在定下了我们须要完成的功用以后,接下来就是只须要在组件中去挪用 action 来完成对应的功用了。

路由设置

在这里我们将运用 vue-router 来做路由,援用 bootstrap 款式。

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>vuex-notes-app</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 VueResource from 'vue-resource';

// 路由模块和HTTP模块
Vue.use(VueResource);
Vue.use(VueRouter);

const router = new VueRouter();

router.map({
  '/index': {
    component: App
  }
});

router.redirect({
  '*': '/index'
});

router.start(App, '#app');

根组件 App.vue

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

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

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

<script>
  import Toolbar from './components/Toolbar';
  import NotesList from './components/NotesList';
  import Editor from './components/Editor';
  import store from './vuex/store';
  import { initStore } from './vuex/actions';

  export default {
    components: {
      Toolbar,
      NotesList,
      Editor
    },
    store,
    vuex: {
      actions: {
        initStore
      }
    },
    ready() {
      this.initStore()
    }
  }
</script>

在根组件中援用了三个子组件:Toolbar.vue, NotesList.vue, Editor.vue。

注重:我们在设置内里加入了 vuex 这么一个选项,这里用来将我们 action 内里定义的要领给暴露出来,我们在根组件中只做了一件事变,那就是初始化模仿数据,因而我们在组件生命周期的 ready 阶段挪用了 actions 内里的 initStore 来初始化我们的 store 内里的 state

Toolbar.vue

<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 { newNote, deleteNote, toggleFavorite } from '../vuex/actions';
import { activeNote } from '../vuex/getters';

export default {
  vuex: {
    getters: {
      activeNote
    },
    actions: {
      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>

在这里,我们用到了 Vuex 的一个案例就是我们须要晓得当前的激活的笔记是不是是珍藏种别的,如果是,我们须要高亮珍藏按钮,那末怎样晓得呢?那就是经由过程 vuex 内里的 getters 猎取当前激活的笔记对象,推断它的 favorite 是不是为 true。

一直切记一个观点,vuex 中数据是单向的,只能从 store 猎取,而我们这个例子中的 activeNote 也是一直都在 store.js 中保护的,如许子就可以给其他组件公用了

// 须要保护的状况
const state = {
  notes: [],
  activeNote: {},
  show: ''
};

NotesList.vue

<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 { updateActiveNote, updateShow } from '../vuex/actions';
  import { show, filteredNotes, activeNote } from '../vuex/getters';

  export default {
    vuex: {
      getters: {
        show,
        filteredNotes,
        activeNote
      },
      actions: {
        updateActiveNote,
        updateShow
      }
    },
    methods: {
      toggleShow(show) {
        this.updateShow(show);
      }
    }
  }
</script>

笔记列表组件,重要有三个操纵

  • 衬着笔记
  • 切换衬着笔记
  • 点击列表 title,切换 activeNote

我们经由过程 getters 中的 filteredNotes 要领猎取笔记列表

// 猎取 noteList,这里将会依据 state.show 的状况做数据过滤
export const filteredNotes = (state) => {
  if(state.show === 'all'){
    return state.notes || {};
  }else if(state.show === 'favorite'){
    return state.notes.filter(note => note.favorite) || {};
  }
};

可以看到,我们猎取的列表是依靠于 state.show 这个状况的。而我们的切换列表操纵正好就是挪用 actions 内里的要领来更新 state.show,如许一来,完成了数据列表的动态革新,而且我们对树的操纵都是经由过程挪用 actions 的要领来完成的。

我们再看,在切换列表的时刻,我们还须要动态的更新 activeNote。看看我们在 store.js 中是怎样做的:

// 切换显现数据列表范例:悉数 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] || {};
  }
}

触发这些操纵的是我们给两个按钮离别绑定了我们自定义的函数,经由过程给函数传入差别的参数,然后挪用 actions 内里的要领,来完成对数据的过滤,更新。

Editor.vue

<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 { editNote } from '../vuex/actions';
  import { activeNote } from '../vuex/getters';

  export default {
    vuex: {
      getters: {
        activeNote
      },
      actions: {
        editNote
      }
    },
    computed: {
      // 经由过程盘算属性获得的一个对象,如许子我们就可以兴奋的运用 v-model 了
      currentNote: activeNote
    },
    methods: {
      // 为何这么做? 因为在严厉形式中不允许直接在模板层面去修正 state 中的值
      updateNote() {
        this.editNote(this.currentNote);
      }
    }
  }
</script>

在 Editor.vue 组件中,我们须要可以及时的更新当前的 activeNote 组件和列表中对应的我们正在修正的笔记对象的内容。

因为我们前面提到过,在组件中是不允许直接修正 store.js在内里的状况值的,所以在这里的时刻,我们经由过程一个盘算属性,将 store 内里的状况值赋值给一个对象,然后在自定义的 updateNotes() 要领中,去挪用 action,同时传入 currentNote 对象。

在 store.js 中,我们是这么做的,找到对应的 id 的对象,从新赋值,因为前面提到过,我们的数据是相应式的,在这里进行了转变,对应的视图也将革新转变,如许一来就完成了及时编辑,及时衬着的功用了。

// 修正笔记
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;
    }
  };
},

Q&A

在这个项目中,我们并没有引入 vue-resource 插件,只是本身模仿了部份的数据,有兴致的同砚可以本身去尝尝。

因为我们的例子相对简朴,没有涉及到很深切的东西,更深条理的研讨须要人人花更多的时候去实践了。

末了,再说一句,在 action 内里,我们实在可以做的另有更多,比方依据 id 动态的异步猎取笔记内容等等,这些有兴致的同砚可以本身去尝试,一点点的雄厚这个例子。

    原文作者:离尘不理人
    原文地址: https://segmentfault.com/a/1190000005891026
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞