若文章中存在内容无法加载的情况,请移步作者其他博客。

最近在看 Vue 的时候,别人给我安利了一个国外的小案例,通过 Vue 和 Vuex 来实现一个记事本。

仔细剖析下,发现“麻雀虽小,五脏俱全”,是一个挺适合初学者学习分析的一个案例,所以自己也将自己的学习过程整理,得出本文。

国际惯例,首先感谢原文作者。

参考案例传送门:

Learn Vuex by Building a Notes App

之后是内容声明:

  • 原文是2016年 4 月 20 日就出现了的,所以很多小伙伴可能已经看过了,但是本文的实现过程却和原文不同,所以,你其实也可以重新看一看的#斜眼笑。
  • 本文仅用于作者记录使用,请勿转载,请随意吐槽。

好了,开始正文。

1. 前期准备


本文中使用了以下内容,在阅读本文前,请保证您对以下内容有了基础的了解。

之前作者写过一篇关于 Vue 基础入门的文章。

里面介绍了一下关于 Vue 的发展前景,以及 Vue 最基础的使用,大家可以选择性的阅读一下。

2.需求分析


首先,如果我们想要制作一个单页应用,我们首先要知道,我们要做什么?

那么,首先来一个草图。

这时候,我们一起来分析一下,当前页面的实现过程。

  • 页面中分为三个部分

    • 左侧工具栏:Toolbar
    • 中间笔记列表:NoteList
    • 右侧编辑区域:Editor
  • 页面样式的设置
  • 在页面的实现过程中,需要完成以下几个方法
    • 新增笔记
    • 修改笔记
    • 删除笔记
    • 切换笔记的收藏与取消收藏
    • 切换显示数据列表类型
    • 设置当前激活的笔记

这时候我们明确了当前内容至少会涉及到页面,页面美化,以及数据处理等。

那我们就可以针对特定的需求来进行特定内容的处理了。

  • 结构:用 Vue-cli 来快速生成
  • 美化:想了想,还是自己手动写吧
  • 数据:选用 Vuex 来集中处理

但是在正式开始文章前,请先了解一下,关于 Vuex 的基础知识。

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
这就是 Vuex 背后的基本思想,借鉴了 FluxRedux、和 The Elm Architecture
与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。

在这里有一个需要注意的内容,就是关于 Vuex 中的 Store。

每一个 Vuex 应用的核心就是 store(仓库)。"store" 基本上就是一个容器,它包含着你的应用中大部分的状态(state)。Vuex 和单纯的全局对象有以下两点不同:

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交(commit) mutations。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

其实说白了,我们的 state 就是我们项目中所有数据的集合,之后通过 Vuex 来区分开实际应用中的 组件本地状态和应用层级状态。

这里需要区分一下,关于 组件本地状态和应用层级状态。

  • 组件本地状态

    • state that is used by only one component (think of the data option you pass to a Vue component)
    • 该状态表示仅仅在组件内部使用的状态,有点类似通过配置选项传入 Vue 组件内部的意思
  • 应用层级状态
    • state that is used by more than one component
    • 应用层级状态,表示同时被多个组件共享的状态层级

如果你明白了上面的内容,那么接下来,我们就可以一起来构建我们的新项目了。

3.项目构建


项目推荐直接使用 Vue 官方提供的脚手架(Vue-cli),所以第一步首先是安装脚手架。

PS: 作者默认大家是对 Vue 有一定的基础了解之后再看的文本,所以如果有哪些步骤不明确,请参考 Vue - 起手式

安装 Vue-cli

npm install -g vue-cli

注意:

  • -g 是直接安装在全局环境下,推荐大家也是如此。
  • 推荐大家确认一下自己当前 node 的版本,尽量是最新版。
  • 如果发生无法安装,请确认是否是权限不足。
    • 如果是权限不足,请在内容前加上 sudo
    • sudo npm install -g vue-cli

创建应用

vue init webpack note
  • webpack 是我们安装内容时所默认使用的模板。
  • note 是我们创建的项目名称
  • 安装过程中,会出现询问你具体项目信息的内容
    • 推荐大家都直接选择拒绝即可。

      • 询问内容:项目名,描述,作者三项,直接回车即可
      • 检查测试:语法检查,单元测试,项目测试三项直接输入 N

进入当前目录

cd /Users/lp1/Desktop/notes    (你当前的文件目录)

安装 Vue 的依赖包

npm install

如果不安装依赖,经常会发生下面这种错误。

启动 Vue 服务

npm run dev

在启动服务的时候,也有可能会遇到 端口被占用 的错误。

第一种解决方案是进入 Vue 中的 index.js 中修改 默认端口号。

第二种是自己去找到被占用的端口,kill 掉它(一般 kill node 的就可以)。

如果这时候页面中已经弹出一个新的页面,则证明你当前的服务启动成功了。

这里就不单纯的介绍项目的内容组成了,具体的可以参考我之前的文章。

4. 项目组件划分


在开始之前,就如我们上面的分析一般,我们需要将我们所要使用的内容进行划分。

作者留言:
Vue 中最重要的两个概念,理解了这两个概念对以后会有很大帮助。

  • 模块化编程
  • 数据驱动

根据页面中的功能,我们可以将页面分成四个大块。

首先第一个肯定是最外层的父级,我们一般直接书写在 App.vue当中。

其次是左中右三部分的组件,我们分别命名并统一放在 components 当中。

  • Toolbar : 工具栏用于对当前内容进行新增和删除
  • NoteList : 列表通过操作 CSS 来高亮我们选中的内容
  • Editor : 编辑器用于显示用户的编辑操作

而最下面的 App.vue 则是所有组件的根。

那我们现在虽然将不同的组件进行了划分,可以划分之后我们该如何去处理三个组件之间的通信呢?

这时候其实就该我们的 Vuex 出马了,Vuex 作为一个“数据中心”,我们可以提前将我们想要的内容,进行提前设置。

5.状态管理


着重强调:
vuex 中数据是单向的,只能从 store 获取,而我们的各种操作也始终都在 store.js 中维护,并以此来给其他组件公用。

那根据我们上面所说,我们需要在 Vuex 文件夹下,创建一个 store.js 文件。

需要注意,这里使用很多 ES6 的语法,并且采用了原文不同的实现方法。

//引入vue及vuex
import Vue from 'vue'
import Vuex from 'vuex' Vue.use(Vuex) //需要维护的状态
const state = {
/*
notes:存储note项
activeNote:当前正在编辑的note项
*/
notes:[],
activeNote:{}
} const mutations = {
//添加笔记
ADD_NOTE(state){
const newNote = {
/*
text:默认文字内容
favorite:收藏
*/
text:"new Note",
favorite:false
}
state.notes.push(newNote)
state.activeNote = newNote
},
//编辑笔记
EDIT_NOTE(state,text){
state.activeNote.text = text
},
// 设置当前激活的笔记
SET_ACTIVE_NOTE(state,note){
state.activeNote = note
},
// 切换笔记的收藏与取消收藏
TOGGLE_FAVORITE(state){
state.activeNote.favorite = !state.activeNote.favorite
},
//删除笔记
DELETE_NOTE(state){ for (var i=0; i<state.notes.length; i++){
if (state.notes[i] == state.activeNote){
state.notes.splice(i, 1)
}
}
state.activeNote = state.notes[0]
}
} const actions = {
/*
actions处理函数接受一个 context 对象
{
state, // 等同于 store.state, 若在模块中则为局部状态
rootState, // 等同于 store.state, 只存在于模块中
commit, // 等同于 store.commit
dispatch, // 等同于 store.dispatch
getters // 等同于 store.getters
}
*/
addNote({commit}){
commit('ADD_NOTE')
},
editNote({commit},text){
commit("EDIT_NOTE",text)
},
updateActiveNote({commit},note){
commit('SET_ACTIVE_NOTE',note)
},
toggleFavorite({commit}){
commit('TOGGLE_FAVORITE')
},
deleteNote({commit}){
commit('DELETE_NOTE')
}
}
const getters = {
/*
Getters 接受 state 作为其第一个参数
state => state.notes为箭头函数等价于:
function (state){
return state.notes
}
*/
notes: state => state.notes,
activeNote: state => state.activeNote
} export default new Vuex.Store({
state,
mutations,
actions,
getters
})

记得处理完我们所需要的数据之后,在 main.js 当中将我们的 store 添加上去。

import Vue from 'vue'
import App from './App'
import store from '../vuex/store' Vue.config.productionTip = false new Vue({
el: '#app',
store,
template: '<App/>',
components: { App }
})

6. 根组件


对于整个 APP 的根,也就是 App.vue 来说,它需要处理的事情非常简单,就是在对应的位置去调用对应的组件即可。

<template>
<div id="app">
<toolbar></toolbar>
<note-list></note-list>
<editor></editor>
</div>
</template>
<!--
李鹏 QQ:3206064928
-->
<script>
import Toolbar from './components/Toolbar'
import NoteList from './components/NoteList'
import Editor from './components/Editor' export default {
components:{
Toolbar,
NoteList,
Editor
}
}
</script>
<style type="text/css">
html, #app {
height: 100%;
} body {
margin: 0;
padding: 0;
border: 0;
height: 100%;
max-height: 100%;
position: relative;
}
</style>

至于调用的组件内部,具体是如何实现的 App.vue 并不关心。

7. Toolbar.vue


关于 Toolbar.vue 的设置就比较简单了,我们只需要调用我们之前设置好的内容就可以。

<template>
<div id="toolbar">
<i @click="addOne" class="glyphicon glyphicon-plus"></i>
<i @click="toggleFavorite" class="glyphicon glyphicon-star" v-bind:class="{starred:activeNote.favorite}"></i>
<i @click="deleteNote" class="glyphicon glyphicon-remove"></i>
</div>
</template> <script>
export default {
computed:{
activeNote(){
return this.$store.getters.activeNote
}
},
methods:{
addOne(){
//通过dispatch分发到actions中的addNote
this.$store.dispatch('addNote')
},
toggleFavorite(){
this.$store.dispatch('toggleFavorite')
},
deleteNote(){
this.$store.dispatch('deleteNote')
}
}
}
</script>
<style type="text/css"> #toolbar {
float: left;
width: 80px;
height: 100%;
background-color: #30414D;
color: #767676;
padding: 35px 25px 25px 25px;
}
#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>

需要注意,在这里,我调用了一下 bootstrap 的图标样式。

这个是在 index.js 当中调用的。

8. NoteList.vue


由于我们之前已经将关于数据部分的内容处理过了,所以在这里,我们只需要进行一下简单的判断,将特定的内容加载即可。

<template>
<div id="notes-list">
<div id="list-header">
<h2>Notes</h2>
<div class="btn-group btn-group-justified" role="group">
<!-- All Notes button -->
<div class="btn-group" role="group">
<button @click="show='all'" type="button" class="btn btn-default" v-bind:class="{active:show=='all'}">
All Notes
</button>
</div>
<!-- Favorites Button -->
<div class="btn-group" role="group">
<button @click="show='favorites'" type="button" class="btn btn-default" v-bind:class="{active:show=='favorites'}">
Favorites
</button>
</div>
</div>
</div>
<!-- render notes in a list -->
<div class="container">
<div class="list-group">
<a v-for="item in notes" class="list-group-item" v-bind:class="{active:activeNote == item}" v-on:click="updateActiveNote(item)" href="#">
<h4 class="list-group-item-heading">
{{item.text}}
</h4>
</a>
</div>
</div> </div>
</template> <script>
export default {
data(){
return {
show:'all'
}
},
computed:{
notes(){
if (this.show=='all'){
return this.$store.getters.notes
}else if(this.show=='favorites'){
return this.$store.getters.notes.filter(note=>note.favorite)
}
},
activeNote(){
return this.$store.getters.activeNote
}
},
methods:{
updateActiveNote(note){
console.log(note)
this.$store.dispatch('updateActiveNote',note)
}
}
}
</script>
<style type="text/css">
#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;
}
</style>

9. Editor.vue


关于编辑区域,只需要做一件事,就是获取当前对应内容的文字即可。

<template>
<div id="note-editor">
<textarea v-bind:value="activeNoteText" v-on:input="editNote" class="form-control"></textarea>
</div>
</template> <script>
export default {
computed:{
activeNoteText(){
return this.$store.getters.activeNote.text
}
},
methods:{
editNote(e){
this.$store.dispatch('editNote',e.target.value)
}
}
}
</script>
<style type="text/css">
#note-editor {
height: 100%;
margin-left: 380px;
} #note-editor textarea {
height: 100%;
border: 0;
border-radius: 0;
}
</style>

10.后记


本文主要是用于记录一下自己的分析过程,如果有哪里出错了,欢迎大家指出。

谢谢大家。

李鹏(MR_LP)
2017年04月17日

Vue 单页应用:记事本的更多相关文章

  1. 如何在vue单页应用中使用百度地图

    作为一名开发人员,每次接到开发任务,我们首先应该先分析需求,然后再思考技术方案和解决方案.三思而后行,这是一个好的习惯. 需求:本项目是采用vue组件化开发的单页应用项目,现需要在项目中引入百度的地图 ...

  2. 解决vue单页路由跳转后scrollTop的问题

    作为vue的初级使用者,在开发过程中遇到的坑太多了.在看页面的时候发现了页面滚动的问题,当一个页面滚动了,点击页面上的路由调到下一个页面时,跳转后的页面也是滚动的,滚动条并不是在页面的顶部 在我们写路 ...

  3. vue单页应用中 返回列表记住上次滚动位置、keep-alive缓存之后更新列表数据 那点事

    实践场景需求 产品列表中,滚动到一定位置的时候,点击查看产品信息,后退之后,需要回到原先的滚动位置,这是常见的需求 所有页面均在router-view中,暂时使用了keep-alive来缓存所有页面, ...

  4. 基于vue单页应用的例子

    代码地址如下:http://www.demodashi.com/demo/13374.html 目录结构 src目录 主要的代码目录 components 存放项目组件 router 路由文件 sto ...

  5. vue 单页应用中微信支付的坑

    vue 单页应用中微信支付的坑 标签(空格分隔): 微信 支付 坑 vue 场景 在微信H5页面(使用 vue-router2 控制路由的 vue2 单页应用项目)中使用微信 jssdk 进行微信支付 ...

  6. Vue 基于node npm & vue-cli & element UI创建vue单页应用

    基于node npm & vue-cli & element UI创建vue单页应用 开发环境   Win 10   node-v10.15.3-x64.msi 下载地址: https ...

  7. 【初恋】vue单页应用开发总结

    vue新人,没有高级技巧 本文主要总结了使用vue-cli脚手架安装开发环境,使用vue.js等进行单页应用开发所遇问题的总结. 技术栈: Vue v1.0.21, vue-resource v0.9 ...

  8. Vue单页式应用(Hash模式下)实现微信分享

    前端微信分享的基本步骤: 一.绑定域名: 先登录微信公众平台进入"公众号设置"的"功能设置"里填写"JS接口安全域名".这个不多说,微信开发 ...

  9. vue单页应用前进刷新后退不刷新方案探讨

    引言 前端webapp应用为了追求类似于native模式的细致体验,总是在不断的在向native的体验靠拢:比如本文即将要说到的功能,native由于是多页应用,新页面可以启用一个的新的webview ...

随机推荐

  1. 【数据结构】bzoj3747Kinoman

    Description 共有m部电影,编号为1~m,第i部电影的好看值为w[i]. 在n天之中(从1~n编号)每天会放映一部电影,第i天放映的是第f[i]部. 你可以选择l,r(1<=l< ...

  2. [洛谷P1040] 加分二叉树

    洛谷题目链接:加分二叉树 题目描述 设一个n个节点的二叉树tree的中序遍历为(1,2,3,-,n),其中数字1,2,3,-,n为节点编号.每个节点都有一个分数(均为正整数),记第i个节点的分数为di ...

  3. 【洛谷 UVA11417】 GCD(欧拉函数)

    我们枚举所有gcd \(k\),求所有\(gcd=k\)的数对,记作\(f(k)\),那么\(ans=\sum_{i=1}^{n}(f(i)-1)*i\).为什么减1呢,观察题目,发现\(j=i+1\ ...

  4. jquery hover事件中 fadeIn和fadeOut 效果不能及时停止

    $(".nav ul li").hover(function () { var id = $(this).attr("id"); $(".nav dl ...

  5. [Leetcode Week2]Merge Intervals

    Merge Intervals题解 原创文章,拒绝转载 题目来源:https://leetcode.com/problems/merge-intervals/description/ Descript ...

  6. BAT 前端开发面经 —— 吐血总结

    更好阅读,请移步这里 聊之前 最近暑期实习招聘已经开始,个人目前参加了阿里的内推及腾讯和百度的实习生招聘,在此总结一下 一是备忘.总结提升,二是希望给大家一些参考 其他面试及基础相关可以参考其他博文: ...

  7. python中的yield生成器详解

    #原创,转载请先联系 在学习生成器之前,必须先了解一下迭代器.因为生成器就是一种特殊的迭代器,而且生成器用起来更加优雅. 迭代器的详解可以参考我的另一篇博文:https://www.cnblogs.c ...

  8. SpringMVC框架入门配置 IDEA下搭建Maven项目(zz)

    SpringMVC框架入门配置 IDEA下搭建Maven项目 这个不错哦 http://www.cnblogs.com/qixiaoyizhan/p/5819392.html

  9. Laravel5.5 生成测试数据

    1.在database/factories/UserFactory.php 中添加 2.在tinker中生成数据 3.数据生成成功

  10. 以前在win7上死活安装不上的pymssql,现在可以安装了

    作SQL发布时,支持了mssql,在linux上,pymssql安装一直没问题,但在windows7上就不可以. 今天要用了,心血来潮,下载了一个新的pymssql的exe文件, 就安装成功了... ...