前言
本文将带大家用VUE3实现TodoList的一个经典案例,也相当于是一个电子版的简易记事本!
先来看效果图:
可以看到需要实现的功能如下:
- 新增:输入任务按回车后,就会添加任务到列表。
- 删除:点击每个任务后的'X',该任务就会在列表中被删除。
- 统计:统计剩余几个任务。
- 清空:点击右下角'clear completed',被选中的任务在该列表被清除不显示,并不是真正的删除。
- 隐藏:当没有任务时,最后一栏里的内容隐藏。
- 状态切换:当点击下面的'Completed Active All'任意按钮时,列表中显示相应的任务。
- 全选按钮:当选择此按钮时可以一键选中全部打“√”或者全部不选中。
思路准备
- 由于App.vue最终还会被挂载到index.html中,所以在App.vue中编写主要代码。
- 在App.vue中先搭建好框架:在template模板中写主要页面代码。首先先写死数据在上面,主要看页面效果是怎样的,好调整。
- 模板页面主要分三部分:头部header、主要内容section、尾部footer。在header部分主要写标题和任务输入框;section部分主要写任务列表项;footer部分主要写按钮选择项和计数。
- 写JS,并更换模板页面中的数据,用动态的数据代替。在setup()入口函数中写数据源和要实现功能的函数
正文
一、创建项目
1、首先通过vue create xxx
创建好项目,其中‘xxx’我们以todolist命名
2、当我们以默认的方式创建项目后,需要删除原先的一些内容,目录结构主要如下:
其中components和assets文件夹下不要有任何东西。
main.js中初始化为如下:
App.vue中初始化如下:
二、编写前端页面
在template模板中编写页面的代码和样式。
1、编写页面主要框架:
在class="todoapp"
的section标签下,主要分头部header、主体section、尾部footer三部分。在header中写标题和任务输入框,输入框前有全选按钮;
在section中分多选框——给选中的项打“√”,ul-li
标签下显示任务列表,包括任务名称和删除按钮;在footer标签中写剩余可做任务个数、已做任务按钮、未做任务按钮、全部任务按钮、清空已做任务按钮。
<template>
<section class="todoapp">
<header class="header">
<h1>简易记事本</h1>
<input type="text" class="new-todo" placeholder="想干的事" @keyup.enter="addTodo" v-model="newTodo">
</header>
<section class="main">
<input type="checkbox" class="toggle-all" id="toggle-all" v-model="allDone">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">//循环遍历每个li标签,循环次数取决于数据项的个数
<li class="todo" v-for="(todo) in filtertodo" :key="todo.id" ><!--for循环要key绑定,保证唯一性 -->
<div class="view">
<input type="checkbox" class="toggle" v-model="todo.completed">//多选框
<label>{{ todo.title }}</label>//任务名
<button class="destroy" @click="removeTodo(todo)"></button>//删除按钮
</div>
</li>
</ul>
</section>
<footer class="footer" v-show="todos.length" v-cloak>//列表中有数据就显示最后一行,没有就不显示
<span class="todo-count">
<strong>{{ left }}</strong> left //剩余可做任务个数
</span>
<span class="todo-choose">
<button class="clear-completed todo-do" @click="showAll">All</button> //显示全部任务按钮
<button class="clear-completed todo-do" @click="showActive">Active</button>//显示可做任务按钮
<button class="clear-completed todo-do" @click="showCompleted">Completed</button>//显示已完成任务按钮
</span>
<button class="clear-completed" @click="removeTo">
Clear completed //清空已完成任务按钮
</button>
</footer>
</section>
</template>
2、编写template模板样式
为了方便,在此项目我在npm库中引入了todomvc-app-css样式,链接为todomvc-app-css - npm (npmjs.com),在终端输入npm i todomvc-app-css
进行下载,详细见于此网页进行查看。或者你们喜欢用自己的样式也行,不过用了这个下载的样式后,类名不能乱写,要用里面已经写好的类名样式才有效,并在main.js中引入该css。
最后下载后在node_modules文件夹下有todomvc-app-css文件夹,在此文件夹下index.css是主要实现样式。除此之外,在此css中我加了一些额外样式:
span.todo-choose{
display: inline-block;
margin-top: -10px;
}
.todo-choose button.todo-do{
padding: 10px;
/* width: 100%;
height: 100%; */
}
button.clear-completed.todo-do{
width: 90px;
}
三、编写JS实现
页面框架搭建好后,就是JS实现了!
主要实现的功能,编写的函数主要如下:
1. 新增:输入任务按回车后,就会添加任务到列表。
思路: 首先在输入框中绑定键盘输入事件@keyup.enter="addTodo"
,以及双向绑定事件 v-model="newTodo"
const addTodo = () => {
if (!state.newTodo) return //若输入框输入的值为空不予加入列表中
state.filtertodo.push({ //加数据到筛选的数组中
id: state.filtertodo.length + 1, //每加入的数据加到列表末尾
title: state.newTodo, //加的内容为为输入的值
completed: false //默认新增的不勾选
})
state.todos.push({ //加数据到todos数组中 todos数组和filtertodo数组同步变化 额外加todos数组是为了后面状态切换可以还原原数据,不必更改原数组
id: state.todos.length + 1,
title: state.newTodo,
completed: false
})
state.newTodo = '' //每次回车加完后重新使输入框的值为空
}
2.全选按钮:点击全选按钮列表全部选中打√或者全部不选中
思路: 在全选按钮中双向绑定 v-model="allDone"
const allDone = computed({ // 当计算属性传入一个对象时,可以对该值进行修改
get: function () { // 默认值 获得值
return remaining.value === 0 //全选中则返回true
},
set: function (value) { //修改值
state.filtertodo.forEach((todo) => { //遍历每个列表项把全选按钮的值赋值给每个列表项
todo.completed = value
})
}
})
3. 删除:点击每个任务后的'X',该任务就会在列表中被删除。
思路: 给"X"按钮绑定点击事件@click="removeTodo(todo)"
const removeTodo = (item) => {
// state.todos.splice(idx, 1)
state.filtertodo = state.todos.filter(todo => todo.id !== item.id) //把点击的该项过滤出去
state.todos = state.todos.filter(todo => todo.id !== item.id)
}
4. 统计:统计剩余几个任务。
思路: 用computed()
函数计算没有被选中的任务的个数
//template
<strong>{{ left }}</strong> left
//js
const remaining = computed(
() => state.todos.filter(todo => !todo.completed).length
)
const left=computed(()=>{
return remaining.value
})
5. 清空:点击右下角'clear completed
',被选中的任务在该列表被清除不显示,并不是真正的删除。
思路: 给此按钮绑定点击事件@click="removeTo"
const removeTo=()=>{
state.filtertodo = state.todos.filter(todo=>!todo.completed)
}//过滤掉已经完成的,返回还没有完成的
6. 隐藏:当没有任务时,最后一栏里的内容隐藏。
思路: 给footer
标签绑定v-show
<footer class="footer" v-show="filtertodo.length" > //数组filtertodo有值就显示,即显示的列表任务有就显示,没有则隐藏
7. 状态切换:当点击下面的'Completed Active All
'任意按钮时,列表中显示相应的任务。
思路: 点击All按钮则显示全部任务,点击Active按钮则显示还没完成的任务,点击Completed按钮则显示已完成的任务。都给各个按钮绑定点击事件。
const showAll=()=>{
return state.filtertodo=state.todos.filter(todo=>!todo.completed||todo.completed)
}
const showActive=()=>{
state.filtertodo = state.todos.filter(todo=>!todo.completed)
}
const showCompleted=()=>{
state.filtertodo = state.todos.filter(todo=>todo.completed)
}
tips:
...toRefs(state)
:将state上的每个属性,都转化为ref形式的响应式数据computed()
:接受一个参数为函数,计算结果readonly;接受一个对象作为参数,对象中会有get,set函数,get用于返回值,set用于修改值- 用
computed
和带有ref
返回的值都要加value
filter
:数组.filter(function(形参1,形参2,形参3){})——形参1:必选,可以理解为过滤数组的每一项item;形参2:可选,当前元素索引值;形参3:当前元素的数组对象。return出来的使条件成立的项,不成立的过滤掉;filter()返回的是一个新数组,不会改变原数组。
具体实现代码如下:
<script>
import { reactive, toRefs, computed } from 'vue'
export default {
setup() { // 入口函数
const state = reactive({ //响应式
newTodo: '',//输入框的值
todos: [ //初始化数据
{ id: '1', title: '吃饭', completed: true },
{ id: '2', title: '睡觉', completed: false }
],
filtertodo:[ //
{ id: '1', title: '吃饭', completed: true },
{ id: '2', title: '睡觉', completed: false }
]
})
const addTodo = () => {
if (!state.newTodo) return
state.filtertodo.push({
id: state.filtertodo.length + 1,
title: state.newTodo,
completed: false
})
state.todos.push({
id: state.todos.length + 1,
title: state.newTodo,
completed: false
})
state.newTodo = ''
}
const removeTodo = (item) => {
// state.todos.splice(idx, 1)
state.filtertodo = state.todos.filter(todo => todo.id !== item.id)
state.todos = state.todos.filter(todo => todo.id !== item.id)
}
const removeTo=()=>{
state.filtertodo = state.todos.filter(todo=>!todo.completed)
}
const remaining = computed(
() => state.todos.filter(todo => !todo.completed).length
)
const allDone = computed({ // 当计算属性传入一个对象时
get: function () { // 默认值
return remaining.value === 0
},
set: function (value) {
state.filtertodo.forEach((todo) => {
todo.completed = value
})
}
})
const left=computed(()=>{
return remaining.value
})
const showAll=()=>{
return state.filtertodo=state.todos.filter(todo=>!todo.completed||todo.completed)
}
const showActive=()=>{
state.filtertodo = state.todos.filter(todo=>!todo.completed)
}
const showCompleted=()=>{
state.filtertodo = state.todos.filter(todo=>todo.completed)
}
// console.log(allDone.value);
return {
...toRefs(state), //解构 newTodo todos
addTodo,
removeTodo,
allDone,
left,
removeTo,
showAll,
showActive,
showCompleted,
}
}
}
</script>
注意: 在这里我用了比较累赘的方法但是好理解——用两个一样数据的数组(
todos、filtertodo
)装同样的对象,这样做是为了让点击下面的切换按钮后可以回到原数据模样,filtertodo主要是将挑选出的数据显示,每次切换时filtertodo数组会改变,而todos数组的数据在切换时将符合条件的赋值给filtertodo并显示出来。
总结
本篇文章就到此为止啦,由于本人经验水平有限,难免会有纰漏,对此欢迎指正。如觉得本文对你有帮助的话,欢迎点赞收藏❤❤❤,您的点赞是持续写作的动力,感谢支持。要是您觉得有更好的方法,欢迎评论,提出建议!