嗨咯!这里是小M. (努力学习VUE版)
在学习了JS基础、JQuery后都各位都接触过TodoList吧。
什么!你问我啥是TodoList,请面壁思过3秒。
今天是VUE写TodoList的专场,走过路过千万不要错过,在不使用组件间通信函数的前提下,用VUE写出最原始的TodoList(运用props完成组件间简单通信)
其实只是因为我还没有学到组件间通信(悄悄哭泣3秒)
默默的更新了本地存储功能(用的localStorage),详情请见 App.vue
1.TodoList功能和基本样式
功能:
todoList类似一个简单的列表记事本,基本功:添加、删除、勾选、统计待办事项
静态样式:
静态样式的完整代码在文章末尾
还有另外一种样式,是将正在进行的事项和已完成事项合并为一个列表,但是是异曲同工之妙。
2.组件化编码流程
直接将代码写在App.vue中,就无法让组件有参与感,VUE是基于组件化的MVVM框架,所以我们在编码前,要对组件、数据、功能进行规划。
1.拆分静态组件
将静态组件按照功能拆分为不同的组件
2.实现动态组件
这一步规划好数据应该为哪种类型、放在哪个组件中、组件的父子关系
3.实现交互
完成事件绑定、数据传递等交互效果
3.分析TodoList
1.拆分todolist组件
根据todolist的功能和静态UI界面,我们大致可以分为6个功能(下文的代码以此为例)
- 添加待办事项(TodoInput)
- 统计全部事项(AllTodo)
- 正在进行事项列表(Doing)
- 已完成事项列表(Done)
- 正在进行事项(ItemDoing)
- 已完成事项(ItemDone)
如果将已完成事项与正在进行的事项合并为一个列表
- 添加待办事项(TodoInput)
- 统计全部事项(AllTodo)
- 事项列表(Todos)
- 事项(Item)
2.实现动态组件
因为每一个Item中都有勾选、事项内容、删除等多个属性,所以用数组对象的形式存储数据为最佳。
todos: [
{id:'001',title:'吃饭',completed:'false'},
{id:'002',title:'睡觉',completed:'false'},
{id:'003',title:'打豆豆',completed:'false'}
]
更严谨的对象形式如下:
{id:nanoid(),title:this.title.trim(),completed:false} //this.title是data中一个对象
1.nanoid
nanoid是一个小巧、安全、URL友好、唯一的 JavaScript 字符串ID生成器。它相对UUID而言更加“轻便”
附上nanoid的GitHub仓库链接,有兴趣的UU 看看官方的GitHub仓库
id对于数据而言是非常重要的存在,id就像身份证号,它是每个对象的唯一标识,如果直接采用001这样的字符串会不太严谨。
通常我们使用UUID自动生成id序列号,但是因为我们案例比较小,所以使用nanoid更加合适。
在控制台输入npm i nanoid 便可以自动安装
npm i nanoid
使用直接在引入,通过函数的形式就可以使用啦
import { nanoid } from 'nanoid';
id:nanoid()
2.title判断
当我们在input框输入文本时,可以对字符串进行去除首尾空格的操作 String.trim()
4.实现交互
利用props实现子到父的组件通信
props可以将数据从父组件传给子组件,如果表达式为一个函数,我们便可以在子组件接收到函数后,再通过传参将数据给到父组件。这里要注意的是,既然要传一个函数表达式,那么就必须在属性前加上:(v-bind),才能编译为表达式,不然就是字符串,达不到效果。
由于几个组件都需要使用相同的数据,所以将数据存储在App中,所以操作也要在App组件中进行,才会有接下来复杂的数据传递,间接证明组件间通信方法是很有必要的。
因为我把doing和done分成两个div中进行展示,所以需要区分这两个数组,删除和打勾函数只需要获得一个id,通过completed的true 或flase判断应该操作哪个列表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rlxDHgqi-1648732883695)(…/…/todolist.png)]
1.TodoInput组件
这部分需要获取input框的value,并且进行是否为空的判断。绑定键盘事件。并且键盘事件触发后,将输入框的文字清除
<template>
<div>
<nav>
<div class="header">
<div class="title">ToDoList</div>
<input type="text" placeholder="在此处输入ToDo" class="insert" @keyup.enter="addTodo" v-model="title">
</div>
</nav>
</div>
</template>
<script>
import { nanoid } from 'nanoid';
export default {
name:"TodoInput",
data() {
return {
title:""
}
},
props:['getTodo'],
methods: {
addTodo(){
if(this.title !=''){
const todoObj = {id:nanoid(),title:this.title.trim(),completed:false}
this.getTodo(todoObj)
this.title=''
}else{
alert("输入不能为空")
return;
}
}
},
}
</script>
<style scoped>
nav {
width: 100%;
height: 50px;
line-height: 50px;
font-size: 2rem;
background: rgba(0, 5, 98, .7);
color: white;
}
.header {
width: 65rem;
height: 50px;
margin: 0 auto;
display: flex;
justify-content: space-between;
}
.title {
font-size: 3rem;
cursor: pointer;
}
.insert {
width: 35rem;
height: 24px;
margin-top: 12px;
text-indent: 10px;
border-radius: 5px;
box-shadow: 0 1px 0 rgb(255 255 255 / 24%), 0 1px 6px rgb(0 0 0 / 45%) inset;
border: 0px;
outline: none;
font-size: 1.5rem;
}
</style>
2.AllTodo组件
这个组件只需要接受App.vue传过来的allNum 用v-text绑定就好
<template>
<div>
<h2>全部事项 <span id="num-all" v-text="allNum">0</span></h2>
</div>
</template>
<script>
export default {
name: "AllTodo",
props:['allNum']
};
</script>
<style scoped>
h2 {
position: relative;
}
span {
position: absolute;
top: 2px;
right: 5px;
display: inline-block;
padding: 0 5px;
height: 20px;
border-radius: 20px;
background: #e6e6fa;
line-height: 22px;
text-align: center;
color: #666;
font-size: 14px;
}
</style>
3.ItemDoing组件
需要接收父组件传过来的doing对象, changeChecked函数, delTodo函数并将id通过这个函数的参数形式传给父组件
<template>
<div>
<li id="onLi">
<input
type="checkbox"
class="doing"
:checked="doing.completed"
@change="isChecked(doing.id)"
/>
<p>{{ doing.title }}</p>
<a @click="del(doing.id)">-</a>
</li>
</div>
</template>
<script>
export default {
name: "ItemDoing",
props: ["doing", "changeChecked", "delTodo"],
methods: {
isChecked(id) {
// console.log(id);
//可以直接修改 但原则上在数组原组件上修改
// this.todo.done= !this.todo.done;
this.changeChecked(id);
},
del(id) {
if(confirm('确认删除吗?'))
{
this.delTodo(id)
}
},
},
};
</script>
<style scoped>
li {
height: 32px;
line-height: 32px;
background: #fff;
position: relative;
margin-bottom: 10px;
padding: 0 45px;
border-radius: 3px;
border-left: 5px solid #629a9c;
box-shadow: 0 1px 2px rgb(0 0 0 / 7%);
}
li input {
position: absolute;
top: 2px;
left: 10px;
width: 22px;
height: 22px;
cursor: pointer;
}
li a {
position: absolute;
top: 2px;
right: 5px;
display: inline-block;
width: 14px;
height: 12px;
border-radius: 14px;
border: 6px double #fff;
background: #ccc;
line-height: 14px;
text-align: center;
color: #fff;
font-weight: bold;
font-size: 14px;
cursor: pointer;
/* 默认删除按钮隐藏 */
display: none;
}
#onLi:hover{
background-color: rgba(98,154,156, .3)
}
#onLi:hover a{
display: block;
}
</style>
4.Doing组件
需要接收父组件传过来的’doings’对象,'doingNum’显示事项条数,changeChecked’函数 ,'delTodo’函数并传参(id)
<template>
<div>
<h2>正在进行 <span id="num-doing" v-text="doingNum">0</span></h2>
<div>
<ul id="onList">
<ItemDoing v-for="(doing) in doings" :key="doing.id" :doing="doing" :changeChecked='changeChecked' :delTodo="delTodo" ></ItemDoing>
</ul>
</div>
</div>
</template>
<script>
import ItemDoing from './ItemDoing.vue';
export default {
components: { ItemDoing },
name: "Doing",
props:['doings','changeChecked','doingNum','delTodo'],
};
</script>
<style scoped>
h2 {
position: relative;
}
span {
position: absolute;
top: 2px;
right: 5px;
display: inline-block;
padding: 0 5px;
height: 20px;
border-radius: 20px;
background: #e6e6fa;
line-height: 22px;
text-align: center;
color: #666;
font-size: 14px;
}
ul {
list-style: none;
}
</style>
5.ItemDone组件
和ItemDoing组件差不多,只是接收的数据为dones数组里的数据
<template>
<div>
<li id="onLi" class="dark">
<input
type="checkbox"
:checked="done.completed"
@change="isChecked(done.id)"
/>
<p>{{ done.title }}</p>
<a @click="del(done.id)">-</a>
</li>
</div>
</template>
<script>
export default {
name: "ItemDone",
props: ["done", "changeChecked", "delTodo"],
methods: {
isChecked(id) {
this.changeChecked(id);
},
del(id) {
if(confirm("确认删除吗?")){
this.delTodo(id);
}
},
},
};
</script>
<style scoped>
li {
height: 32px;
line-height: 32px;
background: #fff;
position: relative;
margin-bottom: 10px;
padding: 0 45px;
border-radius: 3px;
border-left: 5px solid #629a9c;
box-shadow: 0 1px 2px rgb(0 0 0 / 7%);
}
li input {
position: absolute;
top: 2px;
left: 10px;
width: 22px;
height: 22px;
cursor: pointer;
}
li a {
position: absolute;
top: 2px;
right: 5px;
display: inline-block;
width: 14px;
height: 12px;
border-radius: 14px;
border: 6px double #fff;
background: #ccc;
line-height: 14px;
text-align: center;
color: #fff;
font-weight: bold;
font-size: 14px;
cursor: pointer;
/* 默认删除按钮隐藏 */
display: none;
}
.dark {
border-left: 5px solid rgb(128, 128, 128);
opacity: 0.5;
}
#onLi:hover{
opacity: 0.8;
}
#onLi:hover a{
display: block;
}
</style>
5.Done组件
和Doing组件差不多,只是接收的数据为dones数组里的数据
<template>
<div>
<h2>已经完成 <span id="num-done" v-text="doneNum">0</span></h2>
<div>
<ul id="downList">
<item-done v-for="done in dones " :key="done.id" :done="done" :changeChecked='changeChecked' :delTodo="delTodo"></item-done>
</ul>
</div>
</div>
</template>
<script>
import ItemDone from './ItemDone.vue';
export default {
components: { ItemDone },
name: "Done",
props:['dones','changeChecked','doneNum','delTodo']
};
</script>
<style scoped>
h2 {
position: relative;
}
span {
position: absolute;
top: 2px;
right: 5px;
display: inline-block;
padding: 0 5px;
height: 20px;
border-radius: 20px;
background: #e6e6fa;
line-height: 22px;
text-align: center;
color: #666;
font-size: 14px;
}
ul {
list-style: none;
}
.dark {
border-left: 5px solid rgb(128, 128, 128);
opacity: 0.5;
}
</style>
6.App.vue
更新了localSorage本地存储数据的代码
App里面存储了三个数组对象,分别是总共的事项列表:todos 正在进行列表:doings 已完成的列表:dones
需要三个方法 getTodo方法添加输入的todo、changeChecked方法判断id和是否完成,执行事项移动操作、delTodo
** 方法判断id和是否完成,执行删除操作**
<template>
<div id="app">
<!-- 输入todo -->
<todo-input :getTodo="getTodo"></todo-input>
<section>
<all-todo :allNum="this.todos.length "></all-todo>
<doing
:doings="doings"
:changeChecked="changeChecked"
:doingNum="this.doings.length"
:delTodo="delTodo"></doing>
<done
:dones="dones"
:changeChecked="changeChecked"
:doneNum="this.dones.length"
:delTodo="delTodo"></done>
</section>
</div>
</template>
<script>
import AllTodo from "./components/AllTodo.vue";
import Doing from "./components/Doing.vue";
import Done from "./components/Done.vue";
import TodoInput from "./components/TodoInput.vue";
export default {
name: "App",
components: {
TodoInput,
Doing,
Done,
AllTodo,
},
data() {
return {
todos:JSON.parse(localStorage.getItem('todos')) || [],//获取本地的todos 没有就为空数组
doings:[],
dones: [],
};
},
//判断获取到的todos中哪些是已完成、那些正在进行,并赋值到对应的数组中,才能正确渲染数据
beforeMount() {
if(this.todos.length){
this.doings = this.todos.filter(todo => !todo.completed)
this.dones = this.todos.filter(todo => todo.completed)
}
},
methods: {
getTodo(todoObj) {
this.todos.unshift(todoObj);
this.doings.unshift(todoObj);
},
changeChecked(id) {
this.todos.forEach((todo) => {
if (todo.id == id) {
//如果done为true,则是已完成列表
if(todo.completed){
//将true改为false,未勾选
todo.completed = !todo.completed;
//将todo添加到doing中
this.doings.unshift(todo);
//移动完成的事项
this.dones = this.todos.filter(todo => todo.completed )
// console.log("取消完成");
}else{
//将false改为true,勾选
todo.completed = !todo.completed;
//将todo添加到done中
this.dones.unshift(todo)
//移动正在进行的事项
this.doings = this.todos.filter(todo => !todo.completed )
// console.log("完成");
}
}
});
},
//删除todo
delTodo(id){
this.todos.forEach((todo) => {
if (todo.id == id) {
//如果done为true,则是已完成列表
if(todo.completed){
this.dones = this.dones.filter(todo => todo.id != id)
//更新todos
this.todos=this.todos.filter(todo => todo.id != id)
}else{
this.doings = this.doings.filter(todo => todo.id != id)
//更新todos
this.todos=this.todos.filter(todo => todo.id != id)
}
}
})
}
},
watch:{
todos:{
immediate:true,
deep:true,
handler(value){
console.log(value);
localStorage.setItem("todos",JSON.stringify(value))
}
}
}
};
</script>
<style >
@media screen and (max-width: 600px) {
/*最大为600*/
html {
font-size: 40%;
}
}
@media screen and (min-width: 600px) {
html {
font-size: 50%;
}
}
body {
margin: 0;
padding: 0;
font-size: 2rem;
background: #f1f1f1;
}
ul {
list-style: none;
}
section {
margin: 0 auto;
width: 65rem;
}
</style>
静态代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
@media screen and (max-width:600px) {
/*最大为600*/
html {
font-size: 40%;
}
}
@media screen and (min-width:600px) {
html {
font-size: 50%;
}
}
body {
margin: 0;
padding: 0;
font-size: 2rem;
background: #f1f1f1;
}
ul {
list-style: none;
}
nav {
width: 100%;
height: 50px;
line-height: 50px;
font-size: 2rem;
background: rgba(0, 5, 98, .7);
color: white;
}
.header {
width: 65rem;
height: 50px;
margin: 0 auto;
display: flex;
justify-content: space-between;
}
.title {
font-size: 3rem;
cursor: pointer;
}
.insert {
width: 35rem;
height: 24px;
margin-top: 12px;
text-indent: 10px;
border-radius: 5px;
box-shadow: 0 1px 0 rgb(255 255 255 / 24%), 0 1px 6px rgb(0 0 0 / 45%) inset;
border: 0px;
outline: none;
font-size: 1.5rem;
}
section {
margin: 0 auto;
width: 65rem;
}
li {
height: 32px;
line-height: 32px;
background: #fff;
position: relative;
margin-bottom: 10px;
padding: 0 45px;
border-radius: 3px;
border-left: 5px solid #629A9C;
box-shadow: 0 1px 2px rgb(0 0 0 / 7%);
}
li input {
position: absolute;
top: 2px;
left: 10px;
width: 22px;
height: 22px;
cursor: pointer;
}
li a {
position: absolute;
top: 2px;
right: 5px;
display: inline-block;
width: 14px;
height: 12px;
border-radius: 14px;
border: 6px double #FFF;
background: #CCC;
line-height: 14px;
text-align: center;
color: #FFF;
font-weight: bold;
font-size: 14px;
cursor: pointer;
}
.dark {
border-left: 5px solid rgb(128, 128, 128);
opacity: 0.5;
}
h2 {
position: relative;
}
span {
position: absolute;
top: 2px;
right: 5px;
display: inline-block;
padding: 0 5px;
height: 20px;
border-radius: 20px;
background: #E6E6FA;
line-height: 22px;
text-align: center;
color: #666;
font-size: 14px;
}
</style>
</head>
<body>
<nav>
<div class="header">
<div class="title">ToDoList</div>
<input type="text" placeholder="在此处输入ToDo" class="insert">
</div>
</nav>
<section>
<h2>正在进行 <span id="num-doing">0</span></h2>
<div>
<ul id="onList">
</ul>
</div>
<h2>已经完成 <span id="num-done">0</span></h2>
<div>
<ul id="downList">
</ul>
</div>
</section>
</body>
</html>
转载:欢迎转载,但未经作者同意,必须保留此段声明;
小白纯分享,欢迎大佬纠错