Vue CLI 系列之(九)TodoList案例
Todo-list案例
1. 工作情景
-
从0到1编写每个组件的结构和样式
-
已有上一版项目的代码【HTML+CSS+JavaScript】,但不是组件化项目,需要进行改造
首先将HTML中body标签内的部分全部放到App组件的模板中,然后把所有的样式也都放到App组件中,这样整个界面就出来了
先拆结构,再拆样式,拆结构时一层一层的拆,因为有的组件中是包含子组件的,不要一条线走完再走其他的组件,拆出一部分结构就马上拿组件标签进行补充,然后观察界面是否与拆之前一样,如果一样,就说明拆的没问题了,然后继续拆其他部分
2. Todo-list案例
组件化编码流程(通用)
-
实现静态组件:抽取组件,使用组件实现静态页面效果
按功能点拆分组件 =》 区分出各组件之间的父子关系 =》 定义各个组件 =》 各个组件中按需引入组件(至此,整个结构搭建完成)=》使用组件实现静态页面效果
-
展示动态数据:
2.1.数据的类型、名称是什么?
2.2.数据保存在哪个组件?
-
交互——从绑定事件监听开始
3. List组件与Item组件
一堆数据用数组,每个数据中的属性太多了用对象
mounted生命周期中可以读取props拿到的数据
为标签动态增加属性
<!-- :checked="todo.isDel":为标签动态增加属性,todo.isDel的值为true,增加属性checked;否则,没有属性checked -->
<input type="checkbox" :checked="todo.isDel"/>
4. Header组件
-
如何获取到用户输入
(1)通过事件对象拿到用户输入
(2)为输入框增加一个v-model,事件回调中读取v-model绑定的属性
-
将用户的输入包装成一个todo对象【包含id,title,isDel(是否完成)】
-
没有后端的情况下如何生成一定不重复的id【Math.random()、Data.now()、uuid标准】
uuid标准:指定了一套规则,专门用于生成全球唯一的字符串编码
uuid的算法:通过目前所处的地理位置+网卡的MAC地址+内存条的序列号等生成一串全球唯一的字符串
nanoid:uuid的变种,将uuid在一定程度上做了精简
安装nanoid
npm i nanoid
注:如果安装过程卡住或速度很慢,配置 npm 淘宝镜像即可
使用nanoid
// nanoid这个库是用了分别暴露的形式进行暴露的,所以引入方式如下 // 引入的nanoid是一个函数,直接调用即可 import {nanoid} from 'nanoid'
-
-
将包装好的todo对象添加到展示列表中
涉及到组件间通信 =》 两个兄弟组件之间要进行数据的传递
思路1需要高级手段实现,目前还未接触到,暂不考虑
思路2实现方式如下
<!--App组件-->
<!--App组件将方法传递给Header组件-->
<Header :add="add"></Header>
methods:{
// todoObj就是子组件传过来的数据
add(todoObj){
this.todos.unshift(todoObj)
}
}
<!--Header组件-->
<!--Header组件接收到add方法-->
props:['add']
const todoObj = {
id: nanoid(),
title: e.target.value,
isDel: false
}
// 进行了父组件方法的调用,todoObj就是要传递的数据
this.add(todoObj)
思路3实现如下
<!--App组件-->
<!--App组件将数据todos传递给Header组件-->
<Header :todos="todos"></Header>
<!--Header组件-->
<!--Header组件接收到todos-->
props:['todos']
<!--Header组件直接操作todos-->
this.todos.unshift(todoObj)
注:一个组件的data、props、methods、computed中的变量名不能有重名项
5. 勾选/取消勾选操作要引起数据的变化
-
拿到操作的那条数据的id
@click+回调和@change+回调都可
-
事件回调中调用App组件中的方法,传递操作的id,App组件中的方法完成对列表的操作
数据在哪里,操作数据的方法就在哪里【数据定义在哪个组件里,数据相关的操作就要在哪个组件中】
标签的缩进,当一个标签身上的属性过多时,使用这样方式
爷爷组件传递方法给孙子组件,需要借助中间组件【父组件】,中间组件的作用仅仅是过渡
<!--App组件定义了getIsSelected方法并将getIsSelected传给子组件List-->
<List :getIsSelected="getIsSelected"></List>
getIsSelected(isSelected, todo){
let findedTodo = this.todoList.findIndex((item, index)=>{
return item.id === todo.id
})
todo.isDel = isSelected
this.todoList.splice(findedTodo,1, todo)
}
<!--List组件接收后直接传给子组件Item-->
<Item
v-for="todo in todoList"
:key="todo.id"
:thingid="todo.id"
:thingtitle="todo.title"
:isdel="todo.isDel"
:todo="todo"
:getIsSelected="getIsSelected"
></Item>
props:['getIsSelected']
<!--子组件Item拿到方法就可以进行调用了-->
props:['getIsSelected']
this.getIsSelected(newValue, this.todo)
6. 实现删除todo功能
-
高亮当前行,并显示隐藏的删除按钮
方式一:纯CSS实现
<li> <label> <input type="checkbox" :checked="todo.isDel" @change="isSelected(todo.id)"/> <span>{{thingtitle}}</span> </label> <button class="btn btn-danger">删除</button> </li> /* 删除按钮默认隐藏*/ li button { float: right; display: none; margin-top: 3px; } /* 浮动到li时高亮背景*/ li:hover { background-color: #DDDDDD; } /* 浮动到li时删除按钮显示*/ li:hover button { display: block; }
方式二:CSS+JavaScript实现
<!--鼠标移入li为按钮增加显示样式,移出隐藏--> <li @mouseenter="isShow = ''" @mouseleave="isShow = 'display:none'"> <label> <input type="checkbox" :checked="todo.isDel" @change="isSelected(todo.id)"/> <span>{{thingtitle}}</span> </label> <button class="btn btn-danger" :style="isShow">删除</button> </li> data(){ return { // 删除按钮默认隐藏 isShow: 'display:none' } } /* 浮动到li时高亮背景*/ li button { float: right; /* display: none; */ margin-top: 3px; } li:hover { background-color: #DDDDDD; }
-
实现删除操作
在App组件中增加删除方法,传给孙子组件,孙子组件通过事件回调调用该方法完成删除操作
注:事件回调命名时不要使用JavaScript关键字,比如const、var、delete等
7. Footer组件
-
实现底部统计
通过计算属性实现
<!--App组件将todoList传给Footer组件--> <Footer :todoList="todoList"></Footer> <!--Footer组件通过计算属性实现已完成事项数量的显示--> <span>已完成{{finishedCount}}</span> / 全部{{todoList.length}} computed:{ finishedCount(){ return this.todoList.reduce((pre, current)=>{ // +运算符优先级比 ?: 高,如果写在一起必须用括号指定顺序 return pre + (current.isDel ? 1 : 0) }, 0) } }
注:一个计算属性可以通过其他多个计算属性计算得到
-
实现全选/取消全选
通过checked+@change实现
通过v-model+计算属性实现
<!--Footer组件--> <!--定义计算属性isAll--> computed:{ isAll:{ get(){ return this.finishedCount === this.todoCount && this.todoCount > 0 }, set(value){ this.changeAllSelect(value) } } } <!--通过v-model将全选按钮与isAll双向绑定,全选按钮状态一变,isAll也要变,反过来一样--> <!--全选按钮的初始状态是通过调用isAll的getter获取到的,当全选按钮的状态发生改变时,调用isAll的setter--> <input type="checkbox" v-model="isAll"/> props:['todoList','changeAllSelect'] methods:{ checkedAllSelect(e){ // e.target.checked:从事件对象身上获取到点击后全选按钮的选中状态 // changeAllSelect是App组件传过来的方法 this.changeAllSelect(e.target.checked) } } <!--App组件--> <MyFooter :todoList="todoList" :changeAllSelect="changeAllSelect"></MyFooter> methods:{ changeAllSelect(sourceStatus){ this.todoList.forEach((item)=>{ item.isDel = sourceStatus }) } }
8. 总结
-
组件化编码流程:
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
静态组件:定义拆分好的组件,并且定义组件时仅考虑结构和样式【交互、数据、事件统统不考虑】
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用【用:对数据进行读和写操作】,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升【将数据放到需要用到这个数据的组件们共同的父组件上这个行为叫做状态提升,React中也称这种行为叫做状态提升】)。
状态数据:数据一改,整个页面都随之变化,这个数据就是状态数据,简称为状态。
(3).实现交互:从绑定事件开始。
-
props适用于:
通信指的就是传递数据
(1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
-
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
一旦给输入类的元素绑定了v-model,就意味着用户的勾选/输入都会影响到绑定的数据,如果与输入类的元素绑定的是props传过来的值,就修改了对应的值
-
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错【Vue没有监测到,并不是说这样是对的】,但不推荐这样做。
箭头函数的简写
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本