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>