本文對Vue和Vuex有必定基礎的同窗更容易掌握,如對Vue和Vuex不是很熟悉的同窗,請先移步Vue官網自行學習css
在這個教程中,咱們會經過構建一個小米便籤應用來學習怎麼使用Vuex,開始我會簡單的介紹Vuex的一些基礎內容,何時使用以及用Vuex怎麼組織代碼,而後一步一步的把這些概念應用到小米便籤應用裏面。html
廢話很少說,先給你們看一下小米便籤應用的截圖:前端
你能夠從GitHub上下載源碼,這裏是項目源代碼的地址和在線預覽地址,安裝成功後推薦使用chrome的設備模式查看效果更佳。vue
Vuex 是一個專門爲 Vue.js 應用所設計的集中式狀態管理架構,它借鑑了 Flux 和 Redux 的設計思想,但簡化了概念,而且採用了一種爲能更好發揮 Vue.js 數據響應機制而專門設計的實現。webpack
若是你不太理解 Vue.js 應用裏的狀態是什麼意思的話,你能夠想象一下你此前寫的 Vue 組件裏面的 data 字段。Vuex 把狀態分紅組件內部狀態和應用級別狀態:git
舉個例子:好比說有一個父組件,它有兩個子組件。這個父組件能夠用 props 向子組件傳遞數據,這條數據通道很好理解。es6
那若是這兩個子組件相互之間須要共享數據呢?或者子組件須要向父組件傳遞數據呢?這兩個問題在應用體量較小的時候都好解決,只要用自定義事件便可。github
可是隨着應用規模的擴大:web
Vuex 要解決的就是這些問題,Vuex 背後有四個核心的概念:vue-router
下面這張圖完美地解釋了一個 Vuex 應用內部的數據流動:
這張圖的重點:
數據流動是單向的
項目結構:
項目主要文件存放於src目錄下:
新建項目:
使用vue-cli腳手架,可用於快速搭建大型單頁應用。該工具爲現代化的前端開發工做流提供了開箱即用的構建配置。只需幾分鐘便可建立並啓動一個帶熱重載、保存時靜態檢查以及可用於生產環境的構建配置的項目:
# 安裝vue npm install vue # 全局安裝 vue-cli npm install --global vue-cli # 建立一個基於 webpack 模板的新項目 vue init webpack notepad-xiaomi # 安裝依賴,走你 cd notepad-xiaomi # 安裝依賴 npm install muse-ui vue-awesome --save # 安裝vuex npm install vue vuex --save # 運行 npm run dev
使用vue-cli腳手架建立項目時,必定要安裝vue-router插件。
安裝依賴後再main.js中引用
在store文件夾下建立第一個index.js:
import Vue from 'vue' import Vuex from 'vuex' import state from './state' import mutations from './mutation' import * as getters from './getters' import * as actions from './actions' Vue.use(Vuex) export default new Vuex.Store({ state, mutations, getters, actions })
如今我用下面這張圖把應用分解成多個組件,並把組件內部須要的數據對應到 store.js 裏的 state。
App根組件,第一幅圖中的紅色盒子
Header頭部組件,第一幅圖中的綠色盒子
NoteList列表組件,第一幅圖中的橙色盒子
ToolBar工具欄組件,第一幅圖中的藍色盒子(包括刪除和移動按鈕)
Editor編輯組件,第二幅圖,
NoteFolder便籤夾組件,第三幅圖
TrashHeader廢紙簍頭部組件,第四幅圖藍色盒子
TrashNoteList廢紙簍列表組件,第四幅圖灰色盒子
TrashToolBar廢紙簍工具欄組件,第四幅圖黃色盒子
state.js裏面的狀態對象會包含全部應用級別的狀態,也就是各個組件須要共享的狀態。
筆記列表(notes: [])包含了 NodesList 組件要渲染的 notes 對象。當前便籤(activeNote: {})則包含當前編輯的便籤對象,多個組件都須要這個對象。
聊完了狀態state,咱們來看看 mutations, 咱們要實現的 mutation 方法包括:
mutation-types中用於將常量放在單獨的文件中,方便協做開發。
export const NEW_NOTE = 'NEW_NOTE' export const EDIT_NOTE = 'EDIT_NOTE' export const TOGGLE_NOTE = 'TOGGLE_NOTE' export const CANCEL_CHECK = 'CANCEL_CHECK' export const ALL_CHECK = 'ALL_CHECK' export const DELETE_NOTE = 'DELETE_NOTE' export const BACK_SAVE = 'BACK_SAVE' export const TOGGLE_TRASHNOTE = 'TOGGLE_TRASHNOTE' export const CANCEL_TRASHCHECk = 'CANCEL_TRASHCHECk' export const ALL_TRASHCHECK = 'ALL_TRASHCHECK' export const DELETE_TRASHNOTE = 'DELETE_TRASHNOTE' export const RECOVERY_NOTE = 'RECOVERY_NOTE'
首先,建立一條新的便籤,咱們須要作的是:
[types.NEW_NOTE](state) { let newNote = { id: +new Date(), date: new Date().Format('yyyy-MM-dd hh:mm'), content: '', done: false } state.notes.push(newNote) }
而後,編輯便籤須要用筆記內容 content 做參數:
[types.EDIT_NOTE](state, note) { state.activeNote = note; }
剩下的這些 mutations 很簡單就不一一贅述了。整個 store/mutation.js 以下:
import Format from '../libs/dateFormat' import * as types from './mutation-types'; const mutations = { [types.NEW_NOTE](state) { let newNote = { id: +new Date(), date: new Date().Format('yyyy-MM-dd hh:mm'), content: '', done: false } state.notes.push(newNote) }, [types.EDIT_NOTE](state, note) { state.activeNote = note; }, [types.TOGGLE_NOTE](state, note) { state.notes.map((item, i) => { if (item.id == note.id) { item.done = !note.done; } }) if (note.done) { state.deleteNotes.push(note); } else { state.deleteNotes.splice(state.deleteNotes.indexOf(note), 1); } }, [types.CANCEL_CHECK](state) { state.notes.map((item, i) => { item.done = false; }) state.deleteNotes = []; state.isCheck = false; }, [types.ALL_CHECK](state, done) { state.deleteNotes = []; state.notes.map((item, i) => { item.done = done; if (done) { state.deleteNotes.push(item); } else { state.deleteNotes = []; } }) }, [types.DELETE_NOTE](state) { state.deleteNotes.map((item, i) => { item.done = false; state.notes.splice(state.notes.indexOf(item), 1); state.trashNotes.push(item) }) state.isCheck = false; state.deleteNotes = []; }, [types.BACK_SAVE](state, note) { if (note.content != '') return; state.notes.splice(state.notes.indexOf(note), 1); }, [types.TOGGLE_TRASHNOTE](state, note) { state.trashNotes.map((item, i) => { if (item.id == note.id) { item.done = !note.done; } }) if (note.done) { state.deleteTrashNotes.push(note); } else { state.deleteTrashNotes.splice(state.deleteTrashNotes.indexOf(note), 1); } }, [types.CANCEL_TRASHCHECk](state) { state.trashNotes.map((item, i) => { item.done = false; }) state.deleteTrashNotes = []; state.isTrashCheck = false; }, [types.ALL_TRASHCHECK](state, done) { state.deleteTrashNotes = []; state.trashNotes.map((item, i) => { item.done = done; if (done) { state.deleteTrashNotes.push(item); } else { state.deleteTrashNotes = []; } }) }, [types.DELETE_TRASHNOTE](state) { state.deleteTrashNotes.map((item, i) => { state.trashNotes.splice(state.trashNotes.indexOf(item), 1); }) state.deleteTrashNotes = []; state.isTrashCheck = false; }, [types.RECOVERY_NOTE](state) { state.deleteTrashNotes.map((item, i) => { item.done = false; state.notes.unshift(item) state.trashNotes.splice(state.trashNotes.indexOf(item), 1); }) state.deleteTrashNotes = []; state.isTrashCheck = false; } } export default mutations;
接下來聊 actions, actions 是組件內用來分發 mutations 的函數。它們接收 store 做爲第一個參數。比方說,當用戶點擊 Toolbar 組件的添加按鈕時,咱們想要調用一個能分發NEW_NOTE mutation 的 action。如今咱們在 store/文件夾下建立一個 actions.js 並在裏面寫上 newNote函數:
// 建立新便籤 export const newNote = ({ commit }) => { commit(types.NEW_NOTE) }
其餘的這些actions都相似,整個store/actions.js以下:
import * as types from './mutation-types'; //建立新便籤 export const newNote = ({ commit }) => { commit(types.NEW_NOTE) } //編輯便籤 export const editNote = ({ commit }, note) => { commit(types.EDIT_NOTE, note) } //勾選便籤 export const toggleNote = ({ commit }, note) => { commit(types.TOGGLE_NOTE, note) } //取消勾選便籤 export const cancelCheck = ({ commit }) => { commit(types.CANCEL_CHECK) } //所有勾選 export const allCheck = ({ commit }, done) => { commit(types.ALL_CHECK, done) } //刪除便籤 export const deleteNote = ({ commit }) => { commit(types.DELETE_NOTE) } //返回自動保存 export const backSave = ({ commit }, note) => { commit(types.BACK_SAVE, note) } //勾選廢紙簍便籤 export const toggleTrashNote = ({ commit }, note) => { commit(types.TOGGLE_TRASHNOTE, note) } //取消勾選廢紙簍便籤 export const cancelTrashCheck = ({ commit }) => { commit(types.CANCEL_TRASHCHECk) } //全選廢紙簍便籤 export const allTrashCheck = ({ commit }, done) => { commit(types.ALL_TRASHCHECK, done) } //刪除廢紙簍便籤 export const deleteTrashNote = ({ commit }) => { commit(types.DELETE_TRASHNOTE) } //恢復便籤 export const recoveryNote = ({ commit }) => { commit(types.RECOVERY_NOTE) }
最後說一下getters,在Store倉庫裏,state就是用來存放數據,如果對數據進行處理輸出,好比數據要過濾,通常咱們能夠寫到computed中。可是若是不少組件都使用這個過濾後的數據,好比餅狀圖組件和曲線圖組件,咱們是否能夠把這個數據抽提出來共享?這就是getters存在的意義。咱們能夠認爲,getters是store的計算屬性
// 搜索過濾便籤 export const filterNote = (state) => { if (state.search != '' && state.notes.length > 0) { return state.notes.filter(note => note.content.indexOf(state.search) > -1) || {} } else { return state.notes || {} } } // 當前編輯的便籤 export const activeNote = (state) => { return state.activeNote } // 便籤列表佈局 export const layout = state => state.layout // 便籤選中狀態 export const isCheck = state => state.isCheck // 廢紙簍便籤選中狀態 export const isTrashCheck = state => state.isTrashCheck
這樣,在 store文件夾裏面要寫的代碼就都寫完了。這裏麪包括了 state.js 中的 state 和 mutation.js中的mutations,以及 actions.js 裏面用來分發 mutations 的 actions,和getters.js中的處理輸出。
最後這個小結,咱們來實現四個組件 (App, Header,Toolbar, NoteList 和 Editor) 並學習怎麼在這些組件裏面獲取 Vuex store 裏的數據以及調用 actions。
main.js是應用的入口文件,裏面有根實例,咱們要把 Vuex store 加到到這個根實例裏面,進而注入到它全部的子組件裏面:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import store from './store/index' /* 第三方插件 */ import MuseUI from 'muse-ui' import 'muse-ui/dist/muse-ui.css' import 'muse-ui/dist/theme-teal.css' import Icon from 'vue-awesome/components/Icon' import 'vue-awesome/icons/flag' import 'vue-awesome/icons' Vue.use(MuseUI) Vue.component('icon', Icon); Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' })
根組件 App 做爲總的路由入口:
<template> <div id="app"> <router-view/> </div> </template> <script> export default { name: 'App' } </script>
Notepad 組件會 import 其他三個組件:Header,NoteList和ToolBar:
<template> <div class="notepad"> <Header /> <NoteList /> <ToolBar /> </div> </template> <script> import Header from './Header' import NoteList from './NoteList' import ToolBar from './ToolBar' export default { name: 'Notepad', data () { return { } }, components:{ Header, NoteList, ToolBar, } } </script>
Header組件提供搜索和便籤勾選和取消,並統計勾選數量功能,如圖:
對於Header組件來講,搜索框中輸入查詢內容時,須要對便籤列表中的數據進行過濾,在建立state.js的時候就添加了search字段,用於存儲搜索內容,而在getters.js中經過filterNote方法對便籤列表進行過濾,篩選出符合條件的便籤並返回,這時候咱們在NoteList組件中就直接遍歷filterNote方法就能夠實現搜索功能。
store/getters中實現filterNote方法
// 搜索過濾便籤 export const filterNote = (state) => { if (state.search != '' && state.notes.length > 0) { return state.notes.filter(note => note.content.indexOf(state.search) > -1) || {} } else { return state.notes || {} } }
NoteList組件中遍歷filterNote
<li v-for="note in filterNote" :key="note.id" @mousedown="gtouchstart(note)" @mouseup="gtouchend(note)" @touchstart="loopstart(note)" @touchend="clearLoop"> <h4>{{note.date}}</h4> <p>{{note.content}}</p> <mu-checkbox label="" v-model="note.done" class="checkbox" v-show="isCheck"/> </li>
Header組件:
...mapGetters中的...是es6的擴展運算符,不懂的能夠查閱es6文檔
<template> <header class="header" :class="{visible:isVisible}"> <mu-flexbox class="headerTool" :class="{visible:isVisible}"> <mu-flexbox-item order="0" class="flex"> <mu-raised-button v-if="isCheck" label="取消" @click="cancelCheck" class="raised-button"/> <span v-else class="icon" @click="openFolder"><icon name="folder-open"></icon></span> </mu-flexbox-item> <mu-flexbox-item order="1" class="flex" style="text-align:center"> <span v-if="isCheck">{{checkTitle}}</span> <span v-else>{{title}}</span> </mu-flexbox-item> <mu-flexbox-item order="2" class="flex" style="text-align:right"> <mu-raised-button v-if="isCheck" :label="checkBtnTxt" @click="allCheck(!allChecked)" class="raised-button"/> <span v-else> <span class="icon" v-if="layout=='grid'" @click="changeLayout"><icon name="list"></icon></span> <span class="icon" v-else @click="changeLayout"><icon name="th-large"></icon></span> </span> </mu-flexbox-item> </mu-flexbox> <div class="search"> <div class="icon"><icon name="search"></icon></div> <input type="text" v-model="searchTxt" @keyup="search" @focus="searchFocus" @blur="searchBlur"/> </div> </header> </template> <script> import { mapActions,mapGetters } from 'vuex' export default { name: 'Header', data(){ return { title:'便籤', checkBtnTxt:'全選', searchTxt:'', isVisible:false } }, computed:{ ...mapGetters([ 'layout', 'isCheck' ]), //獲取便籤勾選狀態 allChecked(){ return this.$store.state.notes.every(note => note.done) }, //便籤選中數量提示 checkTitle(){ return `已選擇${this.$store.state.deleteNotes.length}項` } }, methods:{ //顯示搜索框 searchFocus(){ this.isVisible = true; }, //隱藏搜索框 searchBlur(){ this.isVisible = false; }, //搜索 search(){ this.$store.state.search = this.searchTxt }, //切換佈局 changeLayout(){ if(this.$store.state.layout == 'list'){ this.$store.state.layout = 'grid' }else{ this.$store.state.layout = 'list' } }, //取消勾選 cancelCheck(){ this.$store.dispatch('cancelCheck') }, //全選切換 allCheck(done){ this.checkBtnTxt = done?'取消全選':'全選' this.$store.dispatch('allCheck',done) }, //打開便籤夾 openFolder(){ this.$router.push({path:'noteFolder'}) } } } </script>
NotesList 組件主要有三個功能:
<template> <ul class="noteList" :class="layout"> <li v-for="note in filterNote" :key="note.id" @mousedown="gtouchstart(note)" @mouseup="gtouchend(note)" @touchstart="loopstart(note)" @touchend="clearLoop"> <h4>{{note.date}}</h4> <p>{{note.content}}</p> <mu-checkbox label="" v-model="note.done" class="checkbox" v-show="isCheck"/> </li> </ul> </template> <script> import { mapGetters,mapActions } from 'vuex' export default { name: 'NoteList', data(){ return { timeOutEvent: 0, Loop:null } }, computed:{ ...mapGetters([ 'filterNote', 'layout', 'isCheck' ]) }, methods:{ //編輯&選中 editNote(note){ if(this.isCheck){ this.$store.dispatch('toggleNote',note); }else{ this.$store.dispatch('editNote',note); this.$router.push({path:'/editor'}) } }, //鼠標按下,模擬長按事件 gtouchstart(note){ var _this = this; this.timeOutEvent = setTimeout(function(){ _this.longPress(note) },500);//這裏設置定時器,定義長按500毫秒觸發長按事件,時間能夠本身改,我的感受500毫秒很是合適 return false; }, //鼠標放開,模擬長按事件 gtouchend(note){ clearTimeout(this.timeOutEvent);//清除定時器 if(this.timeOutEvent!=0){ //這裏寫要執行的內容(尤如onclick事件) this.editNote(note); } return false; }, longPress(note){ this.timeOutEvent = 0; this.$store.state.isCheck = true; this.$store.dispatch('toggleNote',note); }, //手按住開始,模擬長按事件 loopstart(note){ var _this = this; clearInterval(this.Loop); this.Loop = setTimeout(function(){ _this.$store.state.isCheck = true; _this.$store.dispatch('toggleNote',note); },500); }, //手放開結束,模擬長按事件 clearLoop(){ clearTimeout(this.Loop); } } } </script>
Toolbar組件提供給用戶三個按鈕:建立便籤,編輯便籤和移動便籤(移動便籤功能尚未作):
<template> <div class="toolBar"> <div class="toolBtn" v-if="isCheck"> <span class="icon" @click="deleteNote"><icon name="trash-alt"></icon></span> <span class="icon"><icon name="dolly"></icon></span> </div> <div class="addNote" v-else> <div class="float-button mu-float-button" @click="addNote"><icon name="plus"></icon></div> </div> <mu-dialog :open="dialog" title="刪除便籤" @close="close"> 您肯定刪除所選便籤嗎? <mu-flat-button slot="actions" @click="close" primary label="取消"/> <mu-flat-button slot="actions" primary @click="deleteConfirm" label="肯定"/> </mu-dialog> </div> </template> <script> import { mapGetters,mapActions } from 'vuex' export default { name: 'ToolBar', data(){ return { dialog: false } }, computed:{ ...mapGetters([ 'isCheck' ]) }, methods:{ //添加便籤 addNote(){ this.$store.dispatch('newNote'); this.$router.push({path:'editor'}); }, //刪除便籤 deleteNote(){ this.dialog = true; }, //關閉窗口 close () { this.dialog = false; }, //肯定刪除 deleteConfirm(){ this.dialog = false; this.$store.dispatch('deleteNote'); } } } </script>
Editor 組件是最簡單的,它只作兩件事:
從 store 獲取當前筆記activeNote,把它的內容展現在 textarea
在用戶更新筆記的時候,調用 editNote() action
如下是完整的 Editor.vue:
<template> <div class="edit-panel"> <div class="edit-tool"> <span class="back-list" @click="backList"><icon name="angle-left"></icon></span> <span class="date" v-text="activeNote.date"></span> <span class="saveNote" v-show="isShow" @click="backList">完成</span> </div> <textarea v-focus class="edit-area" v-model="activeNote.content" @keyup="editorNote"></textarea> </div> </template> <script> import { mapGetters } from 'vuex' export default { name: 'Editor', data(){ return { content:'', isShow:false } }, created(){ this.content = this.activeNote.content }, computed:{ //獲取正在操做的便籤 ...mapGetters([ 'activeNote' ]) }, directives:{ focus:{ inserted(el){ el.focus(); } } }, methods:{ //返回便籤列表 backList(){ this.$router.push({path:'/'}) this.$store.dispatch('backSave',this.activeNote) }, //完成按鈕顯示&隱藏 editorNote(){ if(this.content != this.activeNote.content){ this.isShow = true; }else{ this.isShow = false; } } } } </script>
這就是一個小米便籤的建立和編輯,還有刪除以及廢紙簍功能這裏就很少說了,功能都很簡單不明白的地方能夠看源代碼,而後本身實戰操做一番,若有寫的不對的地方你們提出來,互相學習互相幫助嘛,謝謝!
來都來了點一下贊吧,你的贊是對我最大的鼓勵^_^