Vue核心:Vue核心:组件化编程(脚手架)
一、静态页面
app.vue
注: MyItem.vue不直接在app.vue中引入,而在MyList.vue中引入
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader/><MyList/><MyFooter/></div></div></div></template>import MyHeader from'./components/MyHeader'import MyList from'./components/MyList'import MyFooter from'./components/MyFooter.vue'exportdefault{name:'App',components:{MyHeader,MyList,MyFooter},}</script><style>/*base*/
body {background: #fff;}.btn {display: inline-block;padding: 4px 12px;
margin-bottom:0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;cursor: pointer;
box-shadow: inset 0 1px 0rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);
border-radius: 4px;}.btn-danger {color: #fff;
background-color: #da4f49;border: 1px solid #bd362f;}.btn-danger:hover {color: #fff;
background-color: #bd362f;}.btn:focus {outline: none;}.todo-container {width: 600px;margin:0 auto;}.todo-container .todo-wrap {padding: 10px;border: 1px solid #ddd;
border-radius: 5px;}</style>
MyHeader.vue
<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认"/></div></template><style>/*header*/.todo-header input {width: 560px;height: 28px;
font-size: 14px;border: 1px solid #ccc;
border-radius: 4px;padding: 4px 7px;}.todo-header input:focus {outline: none;
border-color:rgba(82,168,236,0.8);
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),00 8px rgba(82,168,236,0.6);}</style>
MyList.vue
<template><ul class="todo-main"><li><label><input type="checkbox"/><span>xxxxx</span></label><button class="btn btn-danger" style="display:none">删除</button></li></ul></template>// 在拆 到 MyItem中<template><ul class="todo-main"><MyItem/>// 想要数据多 就继续引入 <MyItem/> 组件</ul></template><style>/*list*/.todo-main {
margin-left: 0px;border: 1px solid #ddd;
border-radius: 2px;padding: 0px;}.todo-empty {height: 40px;
line-height: 40px;border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;}</style>
MyItem.vue
<template><li><label><input type="checkbox"/><span>xxxxx</span></label><button class="btn btn-danger" style="display:none">删除</button></li></template><style>/*item*/
li {
list-style: none;height: 36px;
line-height: 36px;padding:0 5px;
border-bottom: 1px solid #ddd;}
li label {float: left;cursor: pointer;}
li label li input {
vertical-align: middle;
margin-right: 6px;position: relative;top:-1px;}
li button {float: right;display: none;
margin-top: 3px;}li:before {content: initial;}li:last-child {
border-bottom: none;}</style>
二、展示动态的数据
数据的类型、名称是什么
- 一堆要做的事情是一个数组,一个个要做的事情是对象,对象里面的内容=={id,name,done(标识,完成)}==
数据保存在哪个组件
- List组件展示就将数据保存在List中
MyList.vue
- 根据数据决定使用多少次 MyItem
- 把每一条的具体信息对象传递给 MyItem
<template><ul class="todo-main"><MyItem v-for:"todoObj in todos":key="todoObj.key":todo="todoObj"/></ul></template><script>import MyItem from'./MyItem'exportdefault{name:'MyList',components:{MyItem},data(){return{todos:[{id:'001',title:'抽烟',done:true},{id:'002',title:'喝酒',done:false},{id:'003',title:'开车',done:true}]}}}</script>
MyItem.vue
- 接收
- 动态决定是否勾选
<template><li><label><!--动态决定是否勾选--><input type="checkbox":checked="todo.done"/><span>{{todo.title}}</span></label><button class="btn btn-danger" style="display:none">删除</button></li></template><script>exportdefault{name:'MyItem',//声明接收todoprops:['todo'],}</script>
三、交互
组件之间的通信(兄弟、子传父、爷传孙),后面有更好的方式实现
3.1 添加
MyHeader.vue
- 绑定个键盘事件
- 把用户的输入打印
- 获取用户的输入- 方式一:event 事件对象
add(event){ consloe.log(event.target.value)// 获得发生事件对象的元素}
- 方式二:v-model<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model='title' @keyup.enter="add"/>data(){return{title:''}}menthod:{add(event){ consloe.log(this.target)// 获得发生事件对象的元素}}
- 把获取到的数据包装成一个todo对象 id使用uuid 的压缩版本 nanoid (单机版本)
npm i nanoid
- 把对象放到数组的前民(unshift),在List组件中保存数据的todos ,在Header组件输出
- 两个兄弟组件之间直接进行数据传递——暂时实现不了
- 原始间接传递- 把List中的todos[] 给 App,让App通过 props 方式传递给list- 让Header 把todoObj 给App
具体案例实现:
- 在App里定义一个addTodo方法,通过父传子的形式传给MyHeader
- MyHeader调用了addTodo方法,并对App.vue在data.todos中添加一个todo
- App.vue向MyList中传todos,即可达到插入新的事件的效果
App.vue
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"/><MyList :todos="todos"/><MyFoote/></div></div></div></template><script>import MyHeader from'./components/MyHeader'import MyList from'./components/MyList'import MyFooter from'./components/MyFooter.vue'exportdefault{name:'App',components:{MyHeader,MyList,MyFooter},data(){return{//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)todos:[{id:'001',title:'抽烟',done:true},{id:'002',title:'喝酒',done:false},{id:'003',title:'开车',done:true}]}},methods:{//在data.todos中添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)}},}</script>
MyHeader.vue
<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add"/></div></template><script>// 引入 nanoid import{nanoid}from'nanoid'exportdefault{name:'MyHeader',props:['addTodo'],menthod:{add(event){
consloe.log(event.target.value)// 获得发生事件对象的元素//将用户的输入包装成一个todo对象const todoObj ={id:nanoid(),title:event.target.value,done:false}
consloe.log(todoObj)// 方式一:实现 清空数据时操作了domthis.addTodo(todoObj)//清空输入
event.target.value =''}},// 方式二:v-modeldata(){return{//收集用户输入的titletitle:''}},methods:{add(){//校验数据if(!this.title.trim())returnalert('输入不能为空')//将用户的输入包装成一个todo对象const todoObj ={id:nanoid(),title:this.title,done:false}//通知App组件去添加一个todo对象this.addTodo(todoObj)//清空输入this.title =''}},}</script>
MyList.vue
<template><ul class="todo-main"><MyItem v-for:"todoObj in todos":key="todoObj.key":todo="todoObj"/></ul></template><script>import MyItem from'./MyItem'exportdefault{name:'MyList',components:{MyItem},props:['todos'],}</script>
3.2 勾选
MyItem.vue
- 拿到勾选的id,去todos中找到具体的某个人的 done 属性取反
- todos数据在App (数据在哪里操作数据的方法就在哪里)
<template><li><label><!--动态决定是否勾选--><!--change 改变就会触发--><input type="checkbox":checked="todo.done" @change="handleCheck(todo.id)"/><!--
如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props
v-model 绑定的是传递过来的数据 props 不建议
--><!--<input type="checkbox" v-model="todo.done"/>--><span>{{todo.title}}</span></label><button class="btn btn-danger" style="display:none">删除</button></li></template><script>exportdefault{name:'MyItem',//声明接收todoprops:['todo'],methods:{//勾选or取消勾选handleCheck(id){//通知App组件将对应的todo对象的done值取反//checkTodo为App.vue定义的方法this.checkTodo(id)}},}</script>
App.vue
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo":checkTodo="checkTodo"/><MyList :todos="todos"/><MyFoote/></div></div></div></template><script>exportdefault{name:'App',components:{MyHeader,MyList,MyFooter},data(){return{//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)todos:[{id:'001',title:'抽烟',done:true},{id:'002',title:'喝酒',done:false},{id:'003',title:'开车',done:true}]}},methods:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){//通过Item传回的id参数,对todos做遍历,找到对应id的对象,将其done取反this.todos.forEach((todo)=>{if(todo.id === id) todo.done =!todo.done
})}}}</script>
MyList.vue
补充下列代码
<MyItem v-for:"todoObj in todos":key="todoObj.key":todo="todoObj":checkTodo="checkTodo"/>props:['todos','checkTodo']
3.3 删除
- 鼠标悬浮有高亮效果,并出现删除按钮
- 获取id,根据id删除
MyItem.vue 通知app删除对应项 同样是 爷 传 孙
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>...//声明接收todo、checkTodo、deleteTodoprops:['todo','checkTodo','deleteTodo'],methods:{//删除handleDelete(id){//confirm会跳出个弹框让用户选择 确定 或 取消,并返回bool值if(confirm('确定删除吗?')){//通知App组件将对应的todo对象删除this.deleteTodo(id)}}},...<style scoped>li:hover{
background-color: #ddd;}li:hover button{display: block;// 鼠标滑过显示 删除按钮}</style>
App.vue 传 list
<MyList :todos="todos":checkTodo="checkTodo":deleteTodo="deleteTodo"/>...methods:{//删除一个tododeleteTodo(id){// filter 不改变原数组 this.todos =this.todos.filter(todo=> todo.id !== id )}}...
list 接收
<MyItem
v-for="todoObj in todos":key="todoObj.id":todo="todoObj":checkTodo="checkTodo":deleteTodo="deleteTodo"/>...props:['todos','checkTodo','deleteTodo']
3.4 底部统计
- 统计全部和已完成 MyFooter –> todos 数组的长度 done 为真的数量
App.vue 给 footer 传递todos数组
<MyFooter :todos="todos"/>
MyFooter.vue 声明接收
// 1<span>已完成{{todos.???}}</span>/ 全部{{todos.length}}props:['todos'],//2// 等于0 时不展示<div class="todo-footer" v-show="total"><span>已完成{{doneTotal}}</span>/ 全部{{total}}computed:{//总数total(){returnthis.todos.length
},//已完成数// 方式一: 数组中的方法 reduce 推荐doneTotal(){//此处使用reduce方法做条件统计//reduce以todos中的个数作为循环次数,第一次循环以程序员写的0作为pre,current是现在的todos[i]对象//第二次循环以第一次循环的返回值为pre,以此类推//最后一次循环的返回值作为整个函数的返回值,即返回给x/* const x = this.todos.reduce((pre,current)=>{
console.log('@',pre,current)
return pre + (current.done ? 1 : 0)
},0) *///简写returnthis.todos.reduce((pre,todo)=> pre +(todo.done ?1:0),0)}// 方式二:常规遍历doneTotal(){let i =0this.todos.forEach((todo)=>){if(todo.done) i++}return i
}},
3.5 底部交互
- 全选 / 全不选,取决于 已完成 和 全部 是否相等
- 如果没有数据时,不应该勾选,且不应该展示下面整个框
3.5.1 MyFooter.vue 已完成 / 完成数量的动态变化
MyFooter.vue
//1.复杂写法//<input type="checkbox" :checked="doneTotal === tatal"/>//2.vue简便写法//total = 0即没有添加事件时,该模块不显示<div v-show="total"><input type="checkbox":checked="isAll" @change="checkAll"/></div></script>exportdefault{name:'MyFooter',props:['todos','checkAllTodo','clearAllTodo'],computed:{//总数total(){returnthis.todos.length
},//已完成数doneTotal(){returnthis.todos.reduce((pre,todo)=> pre +(todo.done ?1:0),0)},// 简写方式,没有setter 方法 只能被读取不能被修改才可以 后面需要修改//控制全选框// 一个计算属性可以通过其他的计算属性 在进行计算 isAll(){//已完成事件等于全部事件 且 全部事件大于0 才返回真returnthis.doneTotal ===this.total &&this.total >0}},}</script>
3.5.2 MyFooter.vue 全选 和 局部选 的动态绑定
- this.checkAllTodo(e.target.checked) // true false 全选 或者 全不选
- 告诉存储 todos 的人全选全不选
MyFooter.vue
// 全选按钮//方法一:普通方法<input type="checkbox":checked="isAll" @change="checkAll"/>...methods:{checkAll(e){// true,false表示全选 或 全不选,传给app.vue中checkAllTodo方法this.checkAllTodo(e.target.checked)}},// 方法二: v-model(推荐)//注意这里修改的不是props,而是直接修改的todos,所以可以用v-model<input type="checkbox" v-model="isAll"/>...//非简写方式 可读可写computed:{//控制全选框isAll:{//全选框是否勾选get(){returnthis.doneTotal ===this.total &&this.total >0},//isAll被修改时set被调用set(value){this.checkAllTodo(value)}}},
App.vue
<MyFooter :todos="todos":checkAllTodo="checkAllTodo"/>methods:{//全选or取消全选//这个done就是全选框的true或falsecheckAllTodo(done){//遍历每一个小框,将小框的true或false和全选框的选择状态同步this.todos.forEach((todo)=>{
todo.done = done
})},}
3.5.3 批量删除已完成事件
<div class="todo-footer" v-show="total"><label><!--<input type="checkbox":checked="isAll" @change="checkAll"/>--><input type="checkbox" v-model="isAll"/></label><span><span>已完成{{doneTotal}}</span>/ 全部{{total}}</span><button class="btn btn-danger" @click="clearAll">清除已完成任务</button></div>...props:['todos','checkAllTodo',,'clearAllTodo'],methods:{//批量删除已完成事件 clearAll(){this.clearAllTodo()}},
App.vue
//清除所有已经完成的todoclearAllTodo(){this.todos =this.todos.filter((todo)=>{return!todo.done
})}
四、todoList案例总结
五、完整代码
App.vue
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"/><MyList :todos="todos":checkTodo="checkTodo":deleteTodo="deleteTodo"/><MyFooter :todos="todos":checkAllTodo="checkAllTodo":clearAllTodo="clearAllTodo"/></div></div></div></template><script>import MyHeader from'./components/MyHeader'import MyList from'./components/MyList'import MyFooter from'./components/MyFooter.vue'exportdefault{name:'App',components:{MyHeader,MyList,MyFooter},data(){return{//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)todos:[{id:'001',title:'抽烟',done:true},{id:'002',title:'喝酒',done:false},{id:'003',title:'开车',done:true}]}},methods:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done =!todo.done
})},//删除一个tododeleteTodo(id){this.todos =this.todos.filter(todo=> todo.id !== id )},//全选or取消全选checkAllTodo(done){this.todos.forEach((todo)=>{
todo.done = done
})},//清除所有已经完成的todoclearAllTodo(){this.todos =this.todos.filter((todo)=>{return!todo.done
})}}}</script><style>/*base*/
body {background: #fff;}.btn {display: inline-block;padding: 4px 12px;
margin-bottom:0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;cursor: pointer;
box-shadow: inset 0 1px 0rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);
border-radius: 4px;}.btn-danger {color: #fff;
background-color: #da4f49;border: 1px solid #bd362f;}.btn-danger:hover {color: #fff;
background-color: #bd362f;}.btn:focus {outline: none;}.todo-container {width: 600px;margin:0 auto;}.todo-container .todo-wrap {padding: 10px;border: 1px solid #ddd;
border-radius: 5px;}</style>
MyHeader.vue
<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/></div></template><script>import{nanoid}from'nanoid'exportdefault{name:'MyHeader',//接收从App传递过来的addTodoprops:['addTodo'],data(){return{//收集用户输入的titletitle:''}},methods:{add(){//校验数据if(!this.title.trim())returnalert('输入不能为空')//将用户的输入包装成一个todo对象const todoObj ={id:nanoid(),title:this.title,done:false}//通知App组件去添加一个todo对象this.addTodo(todoObj)//清空输入this.title =''}},}</script><style scoped>/*header*/.todo-header input {width: 560px;height: 28px;
font-size: 14px;border: 1px solid #ccc;
border-radius: 4px;padding: 4px 7px;}.todo-header input:focus {outline: none;
border-color:rgba(82,168,236,0.8);
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),00 8px rgba(82,168,236,0.6);}</style>
MyFooter.vue
<template><div class="todo-footer" v-show="total"><label><!--<input type="checkbox":checked="isAll" @change="checkAll"/>--><input type="checkbox" v-model="isAll"/></label><span><span>已完成{{doneTotal}}</span>/ 全部{{total}}</span><button class="btn btn-danger" @click="clearAll">清除已完成任务</button></div></template><script>exportdefault{name:'MyFooter',props:['todos','checkAllTodo','clearAllTodo'],computed:{//总数total(){returnthis.todos.length
},//已完成数doneTotal(){//此处使用reduce方法做条件统计/* const x = this.todos.reduce((pre,current)=>{
console.log('@',pre,current)
return pre + (current.done ? 1 : 0)
},0) *///简写returnthis.todos.reduce((pre,todo)=> pre +(todo.done ?1:0),0)},//控制全选框isAll:{//全选框是否勾选get(){returnthis.doneTotal ===this.total &&this.total >0},//isAll被修改时set被调用set(value){this.checkAllTodo(value)}}},methods:{/* checkAll(e){
this.checkAllTodo(e.target.checked)
} *///清空所有已完成clearAll(){this.clearAllTodo()}},}</script><style scoped>/*footer*/.todo-footer {height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;}.todo-footer label {display: inline-block;
margin-right: 20px;cursor: pointer;}.todo-footer label input {position: relative;top:-1px;
vertical-align: middle;
margin-right: 5px;}.todo-footer button {float: right;
margin-top: 5px;}</style>
MyList.vue
<template><ul class="todo-main"><MyItem
v-for="todoObj in todos":key="todoObj.id":todo="todoObj":checkTodo="checkTodo":deleteTodo="deleteTodo"/></ul></template><script>import MyItem from'./MyItem'exportdefault{name:'MyList',components:{MyItem},//声明接收App传递过来的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件MyItem用的props:['todos','checkTodo','deleteTodo']}</script><style scoped>/*main*/.todo-main {
margin-left: 0px;border: 1px solid #ddd;
border-radius: 2px;padding: 0px;}.todo-empty {height: 40px;
line-height: 40px;border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;}</style>
MyItem.vue
<template><li><label><input type="checkbox":checked="todo.done" @change="handleCheck(todo.id)"/><!--
如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props
v-model 绑定的是传递过来的数据
--><!--<input type="checkbox" v-model="todo.done"/>--><span>{{todo.title}}</span></label><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button></li></template><script>exportdefault{name:'MyItem',//声明接收todo、checkTodo、deleteTodoprops:['todo','checkTodo','deleteTodo'],methods:{//勾选or取消勾选handleCheck(id){//通知App组件将对应的todo对象的done值取反this.checkTodo(id)},//删除handleDelete(id){if(confirm('确定删除吗?')){//通知App组件将对应的todo对象删除this.deleteTodo(id)}}},}</script><style scoped>/*item*/
li {
list-style: none;height: 36px;
line-height: 36px;padding:0 5px;
border-bottom: 1px solid #ddd;}
li label {float: left;cursor: pointer;}
li label li input {
vertical-align: middle;
margin-right: 6px;position: relative;top:-1px;}
li button {float: right;display: none;
margin-top: 3px;}li:before {content: initial;}li:last-child {
border-bottom: none;}li:hover{
background-color: #ddd;}li:hover button{display: block;}</style>
六、TodoList本地监视
关于浏览器本地存储不熟悉的可以看回这篇博客:
使用监视switch,监视数据todos的变化,变化后拿最新的数据存储
- 第一次使用时,没有数据,JSON.parse 读取为空会报错,应该给一个空数组
- 有勾选,监视的是todos下的done属性,所以应该是深度监视 -完整版 deep:true
app.vue
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"/><MyList :todos="todos":checkTodo="checkTodo":deleteTodo="deleteTodo"/><MyFooter :todos="todos":checkAllTodo="checkAllTodo":clearAllTodo="clearAllTodo"/></div></div></div></template><script>import MyHeader from'./components/MyHeader'import MyList from'./components/MyList'import MyFooter from'./components/MyFooter.vue'exportdefault{name:'App',components:{MyHeader,MyList,MyFooter},data(){return{//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)// 第一次使用时,没有数据,JSON.parse 读取为空会报错,应该给一个空数组//不为空则返回JSON对象todos:JSON.parse(localStorage.getItem('todos'))||[]}},watch:{todos:{deep:true,handler(value){
localStorage.setItem('todos',JSON.stringify(value))}}},}</script>
七、TodoList自定义事件
app.vue对MyHeader.vue
<MyHeader @addTodo="addTodo"/>
MyHeader.vue
<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/></div></template><script>import{nanoid}from'nanoid'exportdefault{name:'MyHeader',// 接收App传递的过来的addTodo// props:['addTodo'], 不需要接收了data(){return{title:''}},methods:{add(){if(!this.title.trim())returnalert('输入不能为空')const todoObj ={id:nanoid(),title:this.title,done:false}//通知App组件去添加一个todo对象// this.addTodo(todoObj)this.$emit('addTodo',todoObj,1,2,3)// 触发事件this.title =''}},}</script>
app.vue对MyFooter.vue
// :todos="todos" 是传的数据,不用改<MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
MyFooter.vue
<template><div class="todo-footer" v-show="total"><label><!--<input type="checkbox":checked="isAll" @change="checkAll"/>--><input type="checkbox" v-model="isAll"/></label><span><span>已完成{{doneTotal}}</span>/ 全部{{total}}</span><button class="btn btn-danger" @click="clearAll">清除已完成任务</button></div></template><script>exportdefault{name:'MyFooter',//props:['todos','checkAllTodo','clearAllTodo'],props:['todos'],computed:{total(){returnthis.todos.length
},doneTotal(){returnthis.todos.reduce((pre,todo)=> pre +(todo.done ?1:0),0)},isAll:{get(){returnthis.doneTotal ===this.total &&this.total >0},set(value){// this.checkAllTodo(value)this.$emit('checkAllTodo',value)}}},methods:{//清空所有已完成clearAll(){// this.clearAllTodo()this.$emit('clearAllTodo')}},}</script>
八、 TodoList事件总线![
原本是App –> Mylist –>MyItem 逐层传递
main.js
// 安装全局事件总线//创建vmnewVue({el:'#app',render:h=>h(App),beforeCreate(){Vue.prototype.$bus =this},})
App.vue
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><!--1.1<MyHeader @addTodo="addTodo":checkTodo="checkTodo":deleteTodo="deleteTodo"/>--><MyList :todos="todos"/>// 不给list传<MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/></div></div></div></template><script>exportdefault{//收数据绑定事件总线,身上的自定义事件mounted(){this.$bus.$on('checkTodo',this.checkTodo)// 2.1this.$bus.$on('deleteTodo',this.deleteTodo)// 2.1},beforeDestroy(){this.$bus.$off('checkTodo')// 2.1this.$bus.$off('deleteTodo')// 2.1 },}</script>
MyList.vue
<template><ul class="todo-main"><MyItem
v-for="todoObj in todos":key="todoObj.id":todo="todoObj"<!--1.3:checkTodo="checkTodo"--><!--1.4:deleteTodo="deleteTodo"-->/></ul></template><script>import MyItem from'./MyItem'exportdefault{name:'MyList',components:{MyItem},//声明接收App传递过来的数据// 1.2 props:['todos','checkTodo','clearAllTodo'] // List也不接收props:['todos']}</script>
MyItem.vue
<template><li><label><input type="checkbox":checked="todo.done" @change="handleCheck(todo.id)"/><!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --><!--<input type="checkbox" v-model="todo.done"/>--><span>{{todo.title}}</span></label><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button></li></template><script>exportdefault{name:'MyItem',//声明接收todo// 1.5 props:['todo','checkTodo','deleteTodo'], // Item 也接收不到了props:['todo'],methods:{//勾选or取消勾选handleCheck(id){// this.checkTodo(id)this.$bus.$emit('checkTodo',id)// tem里面触发,绑定事件 2.2},//删除handleDelete(id){// this.deleteTodo(id)this.$bus.$emit('deleteTodo',id)// 2.2}}},}</script>
九、TodoList消息订阅与发布
9.1 删除功能
App.vue 订阅 Item 发布
App.vue
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader @addTodo="addTodo"/><MyList :todos="todos"/><MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/></div></div></div></template><script>import pubsub from'pubsub-js'exportdefault{methods:{//删除一个todo//下划线占位,第一个参数是方法名deleteTodo(_,id){this.todos =this.todos.filter(todo=> todo.id !== id )}},mounted(){this.$bus.$on('checkTodo',this.checkTodo)this.pubId = pubsub.subscribe('deleteTodo',this.deleteTodo)},beforeDestroy(){this.$bus.$off('checkTodo')
pubsub.unsubscribe(this.pubId)},}</script>
MyItem.vue
<script>import pubsub from'pubsub-js'exportdefault{methods:{//删除handleDelete(id){if(confirm('确定删除吗?')){// this.$bus.$emit('deleteTodo',id)
pubsub.publish('deleteTodo',id)}}},}</script>
9.2 TodoList编辑功能
- 新增编辑按钮,点击编辑按钮,变成input框
- 需要修改完后input变回文字,但由于在浏览器中存储了数据,所以刷新还是input,所以需要使用失去焦点事件
- 数据校验输入不能为空
- 点击编辑按钮时,新出现的输入框自动获取焦点
MyItem.vue
<template><li><label><input type="checkbox":checked="todo.done" @change="handleCheck(todo.id)"/><span v-show="!todo.isEdit">{{todo.title}}</span><input
type="text"
v-show="todo.isEdit":value="todo.title"
@blur="handleBlur(todo,$event)"
ref="inputTitle"></label><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button><button v-show="!todo.isEdit"class="btn btn-edit" @click="handleEdit(todo)">编辑</button></li></template><script>import pubsub from'pubsub-js'exportdefault{name:'MyItem',//声明接收todoprops:['todo'],methods:{//勾选or取消勾选handleCheck(id){//通知App组件将对应的todo对象的done值取反// this.checkTodo(id)this.$bus.$emit('checkTodo',id)},//删除handleDelete(id){if(confirm('确定删除吗?')){//通知App组件将对应的todo对象删除// this.deleteTodo(id)// this.$bus.$emit('deleteTodo',id)
pubsub.publish('deleteTodo',id)}},//编辑handleEdit(todo){// 判断 todo 身上是否有 isEdit 属性(正在修改的状态)if(todo.hasOwnProperty('isEdit')){// 有就直接修改
todo.isEdit =true}else{// console.log('@')// 没有添加 $set 添加数据(响应式)this.$set(todo,'isEdit',true)
console.log(todo)}//1.直接写focus会出现一个问题:系统在执行完整个代码才会重载Vue,//而在这过程中input还没有显示,即往一个不存在的input上挂focus,则无法实现//2. 解决方法一:简单实现-使用定时器setTimeout,可不给时间。因为定时器会在该区域代码执行完后再调用//3. 解决方法二(官方写法):$nextTick会在下一次DOM更新结束后执行其指定的回调this.$nextTick(function(){// $nextTick 下一轮 this.$refs.inputTitle.focus()// 拿到输入框获取焦点 focus获取焦点})},//失去焦点回调(真正执行修改逻辑)//e是输入框事件handleBlur(todo,e){
todo.isEdit =falseif(!e.target.value.trim())returnalert('输入不能为空!')this.$bus.$emit('updateTodo',todo.id,e.target.value)}},}</script>
app.vue
<script>exportdefault{methods:{//更新一个todoupdateTodo(id,title){this.todos.forEach((todo)=>{if(todo.id === id) todo.title = title
})}},mounted(){this.$bus.$on('updateTodo',this.updateTodo)},beforeDestroy(){this.$bus.$off('updateTodo')},}</script>
十、TodoList过度与动画
给每件todoThing添加和删除添加动画效果
- 方式一:给todo —>Item
- 方式二:List
方式一:
<template><transition name="todo" apper><li><label><input type="checkbox":checked="todo.done" @change="handleCheck(todo.id)"/><!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --><!--<input type="checkbox" v-model="todo.done"/>--><span v-show="!todo.isEdit">{{todo.title}}</span><input
type="text"
v-show="todo.isEdit":value="todo.title"
@blur="handleBlur(todo,$event)"
ref="inputTitle"></label><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button><button v-show="!todo.isEdit"class="btn btn-edit" @click="handleEdit(todo)">编辑</button></li></transition></template><style scoped>.todo-enter-active{animation: atguigu 0.5s linear;}.todo-leave-active{animation: atguigu 0.5s linear reverse;}
@keyframes atguigu {
from{transform:translateX(100%);}
to{transform:translateX(0px);}}</style>
方式二:List
<template><ul class="todo-main"><transition-group name="todo" appear><!--使用一次,就是一次todo--><MyItem
v-for="todoObj in todos":key="todoObj.id":todo="todoObj"/></transition-group></ul></template><style scoped>.todo-enter-active{animation: atguigu 0.5s linear;}.todo-leave-active{animation: atguigu 0.5s linear reverse;}
@keyframes atguigu {
from{transform:translateX(100%);}
to{transform:translateX(0px);}}</style>
版权归原作者 做一只猫 所有, 如有侵权,请联系我们删除。