Vue.js+Vuex:一個簡單的記事本

寫在前面

最近在學習vue,正好也在掘金看到一篇文章,是作者在學習vue的過程中實現的一個記事本應用。
這個想法真的很棒,因爲記事本的功能並不多,邏輯也很簡單,是一個學習vue的好例子。
於是我也跟風實現了一下,下面就講一下我實現的過程和一些收穫。


UI

先看下UI吧

界面

111111

添加一篇記錄

這裏寫圖片描述

點擊一篇記錄

這裏寫圖片描述

添加一篇記錄爲喜愛

這裏寫圖片描述

查看喜愛記錄

這裏寫圖片描述


使用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裏包含着stateactionsgettersmutations,顯然使用一個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-ifv-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可以通過計算屬性得出,由計算屬性得出的好處是當該屬性改變時可以立即顯示修改後的結果。


說完啦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章