使用vuex和elementui制作一個Todolist應用


前言

前兩天復習了一下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上找到問題的解決方法。如果大家有幸踩到坑,不妨也如法炮制一下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM