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' export default { 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 0 rgba(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), 0 0 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/> // 想要数据多 就继续引入 组件</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'export default {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>export default {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'export default {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'export default {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) // 方式一:实现 清空数据时操作了dom this.addTodo(todoObj) //清空输入event.target.value = '' } }, // 方式二:v-model data() {return {//收集用户输入的titletitle:''}},methods: {add(){//校验数据if(!this.title.trim()) return alert('输入不能为空')//将用户的输入包装成一个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'export default {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>export default {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> export default {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(){return this.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 " /> 3.5 底部交互
- 全选 / 全不选,取决于 已完成 和 全部 是否相等
- 如果没有数据时,不应该勾选,且不应该展示下面整个框
3.5.1 MyFooter.vue 已完成 / 完成数量的动态变化
MyFooter.vue
//1.复杂写法////2.vue简便写法//total = 0即没有添加事件时,该模块不显示<div v-show="total"><input type="checkbox" :checked="isAll" @change="checkAll"/></div></script>export default {name:'MyFooter',props:['todos','checkAllTodo','clearAllTodo'],computed: {//总数total(){return this.todos.length},//已完成数doneTotal(){return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)},// 简写方式,没有setter 方法 只能被读取不能被修改才可以 后面需要修改//控制全选框// 一个计算属性可以通过其他的计算属性 在进行计算 isAll(){ //已完成事件等于全部事件 且 全部事件大于0 才返回真return this.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(){return this.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' export default { 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: { //添加一个todo addTodo(todoObj){ this.todos.unshift(todoObj) }, //勾选or取消勾选一个todo checkTodo(id){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.done = !todo.done }) }, //删除一个todo deleteTodo(id){ this.todos = this.todos.filter( todo => todo.id !== id ) }, //全选or取消全选 checkAllTodo(done){ this.todos.forEach((todo)=>{ todo.done = done }) }, //清除所有已经完成的todo clearAllTodo(){ 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 0 rgba(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' export default { name:'MyHeader', //接收从App传递过来的addTodo props:['addTodo'], data() { return { //收集用户输入的title title:'' } }, methods: { add(){ //校验数据 if(!this.title.trim()) return alert('输入不能为空') //将用户的输入包装成一个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), 0 0 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> export default { name:'MyFooter', props:['todos','checkAllTodo','clearAllTodo'], computed: { //总数 total(){ return this.todos.length }, //已完成数 doneTotal(){ //此处使用reduce方法做条件统计 /* const x = this.todos.reduce((pre,current)=>{ console.log('@',pre,current) return pre + (current.done " /> 七、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' export default { name:'MyHeader', // 接收App传递的过来的addTodo // props:['addTodo'], 不需要接收了 data() { return { title:'' } }, methods: { add(){ if(!this.title.trim()) return alert('输入不能为空') 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>export default {name:'MyFooter', //props:['todos','checkAllTodo','clearAllTodo'],props:['todos'],computed: {total(){return this.todos.length},doneTotal(){return this.todos.reduce((pre,todo)=> pre + (todo.done " /> 八、 TodoList事件总线![
原本是App –> Mylist –>MyItem 逐层传递
main.js
// 安装全局事件总线//创建vmnew Vue({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>export default { //收数据绑定事件总线,身上的自定义事件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'export default {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>export default {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' export default { 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'export default {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'export default {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()) return alert('输入不能为空!')this.$bus.$emit('updateTodo',todo.id,e.target.value)}},}</script>
app.vue
<script>export default {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>