前两天复习了一下vuex,想着做一个todolist实践下知识点,于是结合饿了么的热门ui框架element写了这个demo。文章内容较长,如果对vuex和elementui比较熟悉,可以直接看源码,交互步骤并不复杂,算是一个入门级的vuex和element-ui项目,适合练手。
vue2 + vuex + axios + element-ui
使用vue-cli脚手架创建一个项目,选择手动配置:
vue create vuex-totolist
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 }
]
先在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上的数据成功加载到列表上。
在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)
}
为按钮绑定一个单击事件
<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 = ''
}
在删除链接中绑定删除事件函数,在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)
}
}
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()
}
}
点击复选框时拿到最新的状态,以及当前点击这一行的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
})
}
这里需要用到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>
绑定一个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)
}
根据官方文档,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上找到问题的解决方法。如果大家有幸踩到坑,不妨也如法炮制一下。