前言
前兩天復習了一下vuex,想着做一個todolist實踐下知識點,於是結合餓了么的熱門ui框架element寫了這個demo。文章內容較長,如果對vuex和elementui比較熟悉,可以直接看源碼,交互步驟並不復雜,算是一個入門級的vuex和element-ui項目,適合練手。
源碼下載地址:vuex-todolist
技術棧
vue2 + vuex + axios + element-ui
實現步驟
step01:基礎頁面配置
使用vue-cli腳手架創建一個項目,選擇手動配置:
vue create vuex-totolist
配置內容如下:
安裝element-ui
npm i element-ui -S
在 main.js 中寫入以下內容:
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({
render: h => h(App)
}).$mount('#app')
在App.vue中寫入如下內容
<template>
<div id="app">
<el-input placeholder="請輸入待辦事項" class="td-input" ></el-input>
<el-button type="primary">添加事項</el-button>
<el-main class="td-main" >
<el-table
ref="multipleTable"
:data="list"
style="width: 100%">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
label="待辦事項"
width="350">
<template slot-scope="item">{{ item.row.info }}</template></el-checkbox> -->
</el-table-column>
<el-table-column
prop="name"
label="刪除"
width="50">
<el-button size="mini" class="btn-close" type="primary" icon="el-icon-close" circle></el-button>
</el-table-column>
</el-table>
<div class="footer">
<span>0條剩余</span>
<el-radio-group size="small" v-model="radio1">
<el-radio-button class="btn-radio" label="全部"></el-radio-button>
<el-radio-button class="btn-radio" label="未完成"></el-radio-button>
<el-radio-button class="btn-radio" label="已完成"></el-radio-button>
</el-radio-group>
<el-button>清除已完成</el-button>
</div>
</el-main>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
list: [],
radio1: '全部'
}
}
}
</script>
<style>
#app {
width: 610px;
margin: 0 auto;
}
.td-input {
width: 500px;
margin-right: 10px;
}
.td-main {
width: 500px;
margin-top: 10px;
border-radius: 4px;
border: 1px solid #DCDFE6;
}
.btn-close {
float: right;
}
.el-button--mini.is-circle {
padding: 3px;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
}
</style>
創建一個list.json文件,放入到public目錄中
[
{
"id": 0,
"info": "學習MVC設計模式",
"done": false
},
{ "id": 1, "info": "學習Node.js", "done": false },
{
"id": 2,
"info": "下班去健身房",
"done": false
},
{ "id": 3, "info": "晚上十一點淘寶秒殺", "done": false },
{ "id": 4, "info": "周末自制照燒雞腿", "done": false }
]
step02:實現列表數據的動態加載
先在main.js中加載store
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'
import store from './store' //引入store
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({
store, //掛載store
render: h => h(App)
}).$mount('#app')
安裝axios,通過axios請求列表數據
npm i axios -S
在store.js中引入axios
import axios from 'axios'
接着在actions中定義一個函數getList,接收一個參數context,然后在函數中直接發起get請求,請求的是當前項目里的list.json,請求成功后就可以指定一個回調函數,然后寫一個解構賦值data。
getList (context) {
axios.get('/list.json').then(({ data }) => {
})
}
因為該函數尚未被調用,接下來回到App.vue中定義一個生命周期函數created,在里面發起異步請求this.$store.dispatch('getList'),來觸發actions。App.vue組件被渲染的時候,就會調用該函數。
created () {
this.$store.dispatch('getList')
},
之后在store中,將actions內請求的數據掛載到state中。先定義一個數組list來存放所有的任務列表。
state: {
list: []
}
如果要在actions中為list重新賦值,只能調用mutations為對應的state賦值。在mutations中定義一個方法initList,傳入兩個參數:第一個是固定state,第二個是自定義list(之后誰調用了該函數,就必須傳入一個list)。拿到list后,進行賦值操作,就可以賦值到state上面改變list的值。
mutations: {
initList (state, list) {
state.list = list
}
},
回到actions中,通過context調用commit觸發mutation中的initList。因為data獲取了數據,所以作為參數傳遞到mutations里面去。
actions: {
//調用getList
getList (context) {
//發起請求,拿到返回的數據
axios.get('/list.json').then(({ data }) => {
//調用mutations,傳入initList參數,成功給state中的list賦值
context.commit('initList', data)
})
}
}
進入App.vue中,引入mapState。
import { mapState } from 'vuex'
添加計算屬性,里面調用mapState,傳入一個數組,將數據映射過來。全局的list數據就會變成App.vue里的計算屬性了
computed: {
...mapState(['list'])
}
此時list.json上的數據成功加載到列表上。
step03:實現文本框內容的雙向數據綁定
在store的state中定義一個數據節點inputValue。
state: {
list: [],
//定義數據節點
inputValue: 'test'
}
在App.vue中,將全局的inputValue映射為當前的一個計算屬性。然后綁定到輸入框中。
computed: {
//inputValue映射為計算屬性
...mapState(['list', 'inputValue'])
}
<el-input
placeholder="請輸入待辦事項"
class="td-input"
:value="inputValue">
</el-input>
此時在輸入框中出現了文本,實現了單向數據綁定。
接下來給文本框綁定一個input事件,用來監聽文本框的變化
<el-input
placeholder="請輸入待辦事項"
class="td-input"
:value="inputValue"
@input="handleInputChange">
</el-input>
增加一個methods方法,在里面定義handleInputChange,傳入一個參數val
methods: {
handleInputChange (val) {
}
}
在store的mutations中定義一個函數,傳入state和val兩個參數,為store中的inputValue賦值,將值同步保存到inputValue中
setInputValue (state, val) {
state.inputValue = val
}
接着回到App.vue的methods方法中,使用this.$store.commit調用setInputValue,將文本框中的變化保存到state,實現雙向綁定
handleInputChange (val) {
this.$store.commit('setInputValue', val)
}
step04:實現點擊添加事項
為按鈕綁定一個單擊事件
<el-button type="primary" @click="addItemToList">添加事項</el-button>
向列表中新增 item 項,在方法內判斷用戶輸入的值是否為空,如果不為空,則向store中添加列表項
addItemToList () {
if (this.inputValue.trim().length <= 0) {
return this.$message.warning('添加事項不能為空!')
}
this.$store.commit('addItem')
}
// 回到store,在state中添加
nextId: 5,
//新增一個mutations
addItem (state) {
const obj = {
id: state.nextId,
info: state.inputValue.trim(),
// 完成狀態默認是false
done: false
}
// 將對象追加到list中
state.list.push(obj)
// 添加成功后,id自動遞增,保證不重復
state.nextId++
// 清空文本框
state.inputValue = ''
}
step05:實現刪除事項
在刪除鏈接中綁定刪除事件函數,在methods中添加對應方法
<el-button slot-scope="item" @click="removeItemById(item.row.id)" size="mini" class="btn-close" type="primary" icon="el-icon-close" circle></el-button>
// 很據Id刪除對應的任務事項
removeItemById (id) {
this.$store.commit('removeItem', id)
}
在store中新增一個mutations
// 根據Id刪除對應的任務事項
removeItem(state, id) {
// 根據Id查找對應項的索引
const i = state.list.findIndex(x => x.id === id)
// 根據索引,刪除對應的元素
if (i !== -1) {
state.list.splice(i, 1)
}
}
step06:實現復選框狀態的綁定
element-ui提供了selection-change,當選擇項發生變化時會觸發該事件,需要配合ref使用
<el-table
ref="multipleTable"
@selection-change="handleSelectionChange">
</el-table>
在methods中定義
handleSelectionChange (val) {
this.multipleSelection = val
}
checked () {
this.$nextTick(() => {
this.list.forEach(row => {
this.$refs.multipleTable.toggleRowSelection(row, row.done)
})
})
}
在watch當中觸發checked方法,這一步是為了確保在數據變化后調用toggleRowSelection
watch: {
list: function (val) {
this.checked()
}
}
step07:修改任務完成狀態
點擊復選框時拿到最新的狀態,以及當前點擊這一行的id值,監聽復選框狀態改變的事件。
element-ui給table提供了事件select和select-all,分別為當用戶手動勾選數據行和全選 Checkbox 時觸發的事件。在el-table中通過事件綁定的方式監聽select和select-all
<el-table
@select="statusChanged"
@select-all="statusChangedAll">
</el-table>
在methods中定義方法statusChanged,接收兩個參數,通過第二個參數獲取選中行的id和狀態,賦值到參數對象中,然后觸發mutations修改對應的選中狀態
statusChanged (val, row) {
const param = {
id: row.id,
// 點擊后狀態取反
status: !row.done
}
this.$store.commit('changeStatus', param)
}
在mutations中新增changeStatus接收傳遞過來的參數,先查找索引,根據索引找到對象並修改
changeStatus (state, param) {
const i = state.list.findIndex(x => x.id === param.id)
if (i !== -1) {
state.list[i].done = param.status
}
}
定義方法statusChangedAll,傳入一個參數val,val長度不為0,則全選、否則取消全選,以此觸發對應的mutations,改變狀態
statusChangedAll (val) {
if (val.length !== 0) {
this.$store.commit('changeStatusAll')
} else {
this.$store.commit('cleanStatusAll')
}
}
// 修改全選后的狀態
changeStatusAll (state) {
// console.log(params)
state.list.forEach(row => {
row.done = true
})
},
// 取消全選后的狀態
cleanStatusAll (state) {
state.list.forEach(row => {
row.done = false
})
}
step08:統計未完成任務數量
這里需要用到getters,getters是一個包裝器,不會修改state中的原數據。在list數組中調用filter函數,過濾掉未完成的任務,形成新的數組,將這個數組的長度return
getters: {
// 統計未完成的任務數量
unDoneLength (state) {
return state.list.filter(x => x.done === false).length
}
}
在App.vue中引入,在computed計算屬性中使用
import { mapState, mapGetters } from 'vuex'
computed: {
...mapGetters(['unDoneLength'])
}
剩余條數處修改為
<span>{{unDoneLength}}條剩余</span>
step09:清除未完成任務
綁定一個click事件
<el-button @click="clean">清除已完成</el-button>
在methods中添加方法,觸發對應的mutations
// 清除已完成任務
clean () {
this.$store.commit('cleanDone')
}
在mutations中,過濾未完成的任務,重新賦值給state中的list
cleanDone (state) {
state.list = state.list.filter(x => x.done === false)
}
step10:實現列表狀態的切換顯示
根據官方文檔,Radio 單選框的事件處理函數需要綁定在Radio-group或者Ridio中,這里我們綁定到el-radio-group上面,回調函數是選中的按鈕 label 值
<el-radio-group size="small" v-model="radio1" @change="changeList">
</el-radio-group>
在methods中定義一個方法,觸發對應mutations
changeList (key) {
this.$store.commit('changeViewKey', key)
}
因為組件中的所有數據都存儲在store中,需要先在state中定義一個視圖的key值,默認為全部。當點擊不同選項的時候,將最新的key賦值在上面
viewKey: '全部'
// 修改視圖的關鍵字
changeViewKey(state, key) {
state.viewKey = key
}
將全局的viewKey值映射為App.vue組件中的計算屬性。因為列表所有數據都是store中的list原封不動傳遞過來的,要實現按需切換,需要使用getters對數據進行包裝處理
...mapState(['list', 'inputValue', 'viewKey'])
在store中新增一個getters節點,通過label上的值判斷展示的內容
infolist (state) {
if (state.viewKey === '全部') {
return state.list
}
if (state.viewKey === '未完成') {
return state.list.filter(x => !x.done)
}
if (state.viewKey === '已完成') {
return state.list.filter(x => x.done)
}
return state.list
}
在App.vue中新增一個mapGetters節點,將el-table中的數據源切換為getters事件,watch中也改為infolist
...mapGetters(['unDoneLength', 'infolist'])
<el-table :data="infolist"><el-table>
watch: {
infolist: function (val) {
this.checked()
}
}
小結
我在做這個項目的過程中遇到element-ui的坑還是有一些的,有些百度上沒有解決方案(也可能是我搜的方式不對),最后都是直接看官方文檔、在思否上提問和看github上的issue上找到問題的解決方法。如果大家有幸踩到坑,不妨也如法炮制一下。