写在前面
最近在学习vue,正好也在掘金看到一篇文章,是作者在学习vue的过程中实现的一个记事本应用。
这个想法真的很棒,因为记事本的功能并不多,逻辑也很简单,是一个学习vue的好例子。
于是我也跟风实现了一下,下面就讲一下我实现的过程和一些收获。
UI
先看下UI吧
界面
添加一篇记录
点击一篇记录
添加一篇记录为喜爱
查看喜爱记录
使用vue-cli构建项目
这个项目是用脚手架构建的,有了脚手架,根本不需要自己集结所有工具,只需要几行命令就可以拥有足够强大的构建框架。
# 安装vue-cli
npm install -g vue-cli
# 使用vue-cli初始化项目
vue init webpack note
# 进入到目录
cd note
# 安装依赖
npm install
# 开始运行
npm run dev
构建完成后会发现根目录下有个note文件夹,文件结构如下
main.js就是入口文件
vuex维护状态
vuex主要的工作原理就是其在全局维护一个store,在store里包含着state
、actions
、getters
、mutations
,显然使用一个store维护所以的数据,这样表现层只需要取得状态并显示就好,而修改状态只需要派遣一个事件,action会捕获事件并调用相应的mutations来修改state。
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
notes: [],
activeNote: {},
favorNotes: []
}
const getters = {
notes: (state) => state.notes,
activeNote: (state) => state.activeNote,
favorNotes: (state) => state.favorNotes
}
const actions = {
add_note({commit}) {
return commit('ADD_NOTE');
},
delete_note({commit}) {
return commit('DELETE_NOTE');
},
add_favor({commit}) {
return commit('ADD_FAVOR');
},
remove_favor({commit}) {
return commit('REMOVE_FAVOR');
},
edit_note({commit}, text) {
return commit('EDIT_NOTE', text);
},
set_activenote({commit}, item) {
return commit('SET_ACTIVENOTE', item);
}
}
const mutations = {
ADD_NOTE(state) {
//根据id判断是否为同一个note
const noteid = Math.round(Math.random()*10000);
const note = {
id: noteid,
text: 'New Note, say somthing...',
favor: false
}
state.notes.push(note);
console.log(state.notes);
},
DELETE_NOTE(state) {
let notes = state.notes;
for(let key in notes) {
if(notes[key].id == state.activeNote.id) {
state.notes.splice(key, 1);
}
}
state.activeNote = state.notes[0];
},
ADD_FAVOR(state) {
state.activeNote.favor = true;
},
REMOVE_FAVOR(state) {
state.activeNote.favor = false;
},
EDIT_NOTE(state, text) {
state.activeNote.text = text;
for(let i in state.notes) {
if(i == state.activeNote) {
i.text = text;
}
}
},
SET_ACTIVENOTE(state, item) {
state.activeNote = item;
}
}
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
App.js
<template>
<div id="app">
<tool-bar></tool-bar>
<note-list></note-list>
<edit-note></edit-note>
</div>
</template>
<script>
import EditNote from './components/editNote';
import ToolBar from './components/toolBar';
import NoteList from './components/noteList';
export default {
components: {
EditNote,
ToolBar,
NoteList
},
name: 'app'
}
</script>
<style>
html, #app {
height: 100%;
}
#app {
display: flex;
flex-direction: row;
}
body {
margin: 0;
padding: 0;
border: 0;
height: 100%;
max-height: 100%;
position: relative;
}
tool-bar, note-list, edit-note {
display: inline-block;
}
edit-note {
flex: 1;
}
</style>
toolBar.vue
<template>
<div class="tool-list">
<i class="tool-btn glyphicon glyphicon-plus" v-on:click="addNote"></i>
<i class="tool-btn glyphicon glyphicon-star" v-on:click="toggleFavor" v-bind:class="{favor:isFavor}"></i>
<i class="tool-btn glyphicon glyphicon-remove" v-on:click="deleteNote"></i>
</div>
</template>
<script>
export default {
computed: {
isFavor() {
return this.$store.getters.activeNote.favor;
}
},
methods: {
addNote() {
this.$store.dispatch('add_note');
},
deleteNote() {
this.$store.dispatch('delete_note', this.$store.getters.activeNote);
},
toggleFavor() {
var isFavor = this.$store.getters.activeNote.favor? false:true;
if(isFavor)
this.$store.dispatch('add_favor');
else
this.$store.dispatch('remove_favor');
}
}
}
</script>
<style>
.tool-list {
width: 100px;
height: 100%;
background-color: darksalmon;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 10px;
}
.tool-btn {
font-size: 30px;
display: inline-block;
margin: 10px 0;
}
.favor {
color: blanchedalmond;
}
</style>
noteList.vue
<template>
<div class="note-list">
<h3>NOTES</h3>
<div class="list-wraper">
<div class="tab">
<div v-bind:class="{active:showAll==true}" v-on:click="showAllNotes">All Notes</div>
<div v-bind:class="{active:showAll==false}" v-on:click="showFavorNotes">Favorites</div>
</div>
<ul class="show-all" v-if="showAll">
<li v-for="item in notes" v-bind:class="{activeNote:item==activeNote}" v-on:click="clickNote(item)">
{{item.text}}
</li>
</ul>
<ul class="favorites" v-else>
<li v-for="item in favornotes" class="note" v-bind:class="{activeNote:item==activeNote}" v-on:click="clickNote(item)">
{{item.text}}
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
data() {
return {
showAll: true
}
},
computed: {
notes() {
return this.$store.getters.notes;
},
favornotes() {
return this.$store.getters.notes.filter(note => {
return note.favor;
})
},
activeNote() {
return this.$store.getters.activeNote;
}
},
methods: {
showAllNotes: function() {
this.showAll = true;
},
showFavorNotes: function() {
this.showAll = false;
},
clickNote: function(item) {
this.$store.dispatch('set_activenote', item);
}
}
}
</script>
<style>
.note-list {
width: 300px;
display: flex;
flex-direction: column;
align-items: center;
background-color: whitesmoke;
}
.list-wraper {
display: flex;
flex-direction: column;
align-content: center;
}
.tab {
margin: 0 auto;
margin-bottom: 10px;
}
.tab div{
cursor: pointer;
display: inline-block;
border: 1px solid #ddd;
border-radius: 1px;
padding: 2px 14px;
}
.active {
background-color: darksalmon;
color: white;
}
.show-all, .favorites {
width: 300px;
margin: 0;
padding: 0;
}
.show-all li, .favorites li{
overflow: hidden;
word-wrap: break-word;
height: 50px;
margin: 0;
list-style: none;
border-bottom: 1px solid #ddd;
padding: 5px 10px;
}
.activeNote {
background-color: blanchedalmond;
}
</style>
editNote.vue
<template>
<div class="text-wraper">
<button v-on:click="saveNote">SAVE</button>
<textarea class="text-input" v-on:input="saveText" v-bind:value="text"></textarea>
</div>
</template>
<script>
export default {
data: {
textInput: ''
},
computed: {
text() {
this.textInput = this.$store.getters.activeNote.text;
if(this.$store.getters.activeNote.text == undefined)
return '';
return this.$store.getters.activeNote.text;
}
},
methods: {
saveText(e) {
this.textInput = e.target.value;
},
saveNote() {
if(this.textInput) {
this.$store.dispatch('edit_note', this.textInput);
}
}
}
}
</script>
<style>
.text-wraper {
width: 100%;
}
.text-input {
width: 100%;
height: 80%;
border: none;
outline: none;
padding: 20px;
font-size: 15px;
}
button {
width: 100%;
background-color: darksalmon;
color: white;
border: none;
outline: none;
}
</style>
改进
创建第一篇记录时要有focus
我发现如果一开始创建第一篇记录,此时没有选中状态。现在给它加上
显然这是在mutation的ADD_NOTE加上的
ADD_NOTE(state) {
const noteid = Math.round(Math.random()*10000);
const note = {
id: noteid,
text: 'New Note, say somthing...',
favor: false
}
state.notes.push(note);
//新增
if(state.notes.length == 1) {
state.activeNote = state.notes[0];
}
},
删除全部记录后输入区还有记录
这是显示的问题,因此一定是在组件editNote中修改,又因为其内容绑定的是activeNote的内容,说明1.没有清空activeNote 2.没有对activeNote为空进行判断。
- 清空activeNote
DELETE_NOTE(state) {
let notes = state.notes;
for(let key in notes) {
if(notes[key].id == state.activeNote.id) {
state.notes.splice(key, 1);
}
}
if(state.notes.length != 0)
state.activeNote = state.notes[0];
else
state.activeNote = {}
},
- 对activeNote为空进行判断
由于输入区绑定的是text,所以当activeNote改变时就会重新计算。
text() {
this.textInput = this.$store.getters.activeNote.text;
if(this.$store.getters.activeNote.text == undefined)
return '';
return this.$store.getters.activeNote.text;
}
简化代码:改进收藏部分
可以看下原来的代码,其实可以把收藏的加入和取消合并一下
//actions
add_favor({commit}) {
return commit('ADD_FAVOR');
},
remove_favor({commit}) {
return commit('REMOVE_FAVOR');
},
//mutations
ADD_FAVOR(state) {
state.activeNote.favor = true;
},
REMOVE_FAVOR(state) {
state.activeNote.favor = false;
},
改进后
//actions
toggle_favor({commit}) {
return commit('TOGGLE_FAVOR');
},
//mutations
TOGGLE_FAVOR(state) {
state.activeNote.favor = state.activeNote.favor?false:true;
},
简化代码:将收藏列表和全部列表合并
原先的代码中,我使用了两个无序列表来显示两个列表,使用v-if
和v-else
来显示一个隐藏一个,但是后来想想其实没必要啊。
在状态中也是通过filter来过滤收藏列表的,本质上还是一个状态。
<ul class="show-all" v-if="showAll">
<li v-for="item in notes" v-bind:class="{activeNote:item==activeNote}" v-on:click="clickNote(item)">
{{item.text}}
</li>
</ul>
<ul class="favorites" v-else>
<li v-for="item in favornotes" class="note" v-bind:class="{activeNote:item==activeNote}" v-on:click="clickNote(item)">
{{item.text}}
</li>
</ul>
改进后
//模板
<ul class="list">
<li v-for="item in notes" v-bind:class="{activeNote:item==activeNote}" v-on:click="clickNote(item)">
{{item.text}}
</li>
</ul>
//computed
notes() {
if(this.showAll)
return this.$store.getters.notes;
else
return this.$store.getters.notes.filter(note => {
return note.favor;
})
},
改进大概就先这些。到时候想到再来编辑
思考
我觉得整个实现下来我真的对vue以及vuex刮目相看,一开始逻辑不通的时候感觉使用vuex简直乱死了。
但是整理逻辑后会发现其实只维护一个state是很有道理的,它可以在最顶层传入state并在各个表现层拿到后显示出来。
就比如有noteList和editNote两个组件,一个组件时显示记录内容一个则是修改记录内容的。如果不使用vuex,数据将在这二者之间传来传去,肯定麻烦死。
还有一些很酷的语法
v-bind:class=”{style : condition}”
这个语法的意思就是使用v-bind绑定class,若condition为true,则添加style这个class。
为什么要叫做style呢?因为一般这个class都是用来控制样式的。
v-on:click=”func(args)”
其实一开始我是很疑惑点击修改样式是如何做到的。我也知道有v-on:click设置监听函数,但是我不知道可以传参啊!
v-for=”item in notes”
v-for一般用于列表渲染
此处的notes可以通过计算属性得出,由计算属性得出的好处是当该属性改变时可以立即显示修改后的结果。
说完啦