Vue 学习总结笔记 (七)

10 篇文章 0 订阅
本文详细介绍了Vue组件之间的通信,包括自定义事件的使用、父子组件通信及全局事件总线的实现。同时,讲解了浏览器的本地存储机制,如localStorage和sessionStorage的使用,并结合实例展示了如何在Vue应用中保存和恢复待办事项列表。最后,通过修改代码实现了全局事件总线在待办事项列表中的应用,确保数据在页面刷新后仍能保留。
摘要由CSDN通过智能技术生成

文章目录

1. 浏览器本地存储

1.1 浏览器本地存储


浏览器的本地存储:

  • Local Storage
  • SessionStorage

可以在浏览器的开发者工具看到:
在这里插入图片描述
(这就是浏览器本地存储,点x号可以删除当前选中行。)

1.2 localStorage存储


localStorage的存储:
在这里插入图片描述

对于上面的Object,Object,我们可以通过JSON.stringify()方法来字符串化:

在这里插入图片描述

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>localStorage</title>
	</head>
	<body>
		<h2>localStorage的演示</h2>
		<button onclick="saveData()">点我保存一个数据</button>
		<button onclick="readData()">点我读取一个数据</button>
		<button onclick="deleteData()">点我删除一个数据</button>
		<button onclick="deleteAllData()">清空localStorage</button>
		<script type="text/javascript">
			function saveData(){
				let p = {
					name:"张三",
					age:18
				};
				//可以window可以简写忽略
				//需要注意的是这里的两个参数都必须是字符串。
				window.localStorage.setItem('msg','hello,world');
				//666会转成字符串形式
				localStorage.setItem('msg2',666);
				//如果传入的是对象,它就会显示Object纯字符串。
				localStorage.setItem('person',JSON.stringify(p));
			}
			
			function readData(){
				var s1 = localStorage.getItem("msg");
				var s2 = localStorage.getItem("msg2");
				var s3 = localStorage.getItem("person");
				console.log(s1);
				console.log(s2);
				console.log(JSON.parse(s3));
			}
			
			function deleteData(){
				localStorage.removeItem('msg2');
			}
			
			function deleteAllData(){
				localStorage.clear();
			}
		</script>
	</body>
</html>

localStorage存储最大的特点就是关闭浏览器,再打开浏览器它的数据依然会存在。

localStorage中的数据什么时候会消失:

  • 当用户点击了一些功能按钮,操作了remove或clear方法,会让localStorage中的数据消失。
  • 当用户主动的清空浏览器的缓存时,也会导致它消失。

1.3 sessionStorage


sessionStorage中存储数据的生命周期是一个会话时期。

功能方法也都和localStorage的操作相同,唯一要注意的就是这个数据的存活时长。

1.4 总结


localStorage和sessionStorage统称为webStorage。

在这里插入图片描述

2. 浏览器本地存储的 todoLists案例


在我们上个todoLists案例中,加上浏览器本地存储的效果。

  • 最主要的要看App.vue中todolist和watch的实现。

App.vue文件:

<template>
	<div id="root">
		<div class="todo-container">
			<div class="todo-wrap">
				<Header1 :receive="receive"></Header1>
				<List1 
					:todoList="todoList" 
					:checkTodoObj="checkTodoObj"
					:deleteTodoObj="deleteTodoObj"
				></List1>
				<Footer1 :todoList="todoList" :checkAllTodoObj="checkAllTodoObj" :clearAllTodoObj="clearAllTodoObj"></Footer1>
			</div>
		</div>
	</div>
</template>

<script>
	import Header1 from "./components/MyHeader.vue"
	import Footer1 from "./components/MyFooter.vue"
	import List1 from "./components/MyList.vue"
	
	export default{
		name:'App',
		components:{
			Header1,
			List1,
			Footer1,
		},
		data(){
			return {
				//获取当前页面已经存储的localStorage数据。
				//如果localStorage.getItem('todoList')没有数据返回就返回了null,并且JSON解析出来也是null,这样就报错了!
				//所以我们要加一个||或,前面赋值报错就只用后面的[]空数组。
				todoList:JSON.parse(localStorage.getItem('todoList')) || []
			}
		},
		methods:{
			//从MyHeader中获取数据,添加todoObj对象
			receive(todoObj){
				//这样将值传给添加到todoList末尾中。
				this.todoList.unshift(todoObj);
			},
			//勾选或者取消勾选一个todo
			checkTodoObj(id){
				this.todoList.forEach((todoObj)=>{
					if(todoObj.id == id)
						todoObj.done = !todoObj.done
				})
			},
			//删除一个TodoObj
			deleteTodoObj(id){
				//注意:过滤出来的是一个新数组,并不是改变了data中的todoList。
				//因此,要重新赋值一下。
				// console.log(id)
				this.todoList = this.todoList.filter((todoObj)=>{
					return todoObj.id !== id
				})
			},
			//全选or取消全选
			checkAllTodoObj(done){
				this.todoList.forEach((todo)=>{
					todo.done = done
				})
			},
			//清除所有已经完成的todoObj
			clearAllTodoObj(){
				this.todoList = this.todoList.filter((todo)=>{
					return !todo.done
				})
			}
		}
		,
		//实现本地存储
		watch:{
			todoList:{
				//开启深度监视,确保对象中的内容被修改了也能重新定义localStorage中的todoList。
				deep:true,
				handler(newValue,oldValue){
					localStorage.setItem('todoList',JSON.stringify(newValue));
				}
			}
		}
	}
</script>

<style>
	body{
		background-color: #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="请输入你的任务名称,按回车确认" @keyup.enter="add">
	</div>
</template>

<script>
	import {nanoid} from 'nanoid'
	export default {
		name:"MyHeader",
		methods:{
			add(e){
				//判断是否为空
				if(!e.target.value.trim())
					return alert('输入不能为空!')
					
				//获取用户输入信息
				console.log(e.target.value)
				
				//包装用户信息,id使用Nanoid来操作
				const todoObj = {
					id:nanoid(),
					title:e.target.value,
					done:false
				}
				
				//当前vc拿到receive方法,直接传值就可以了。
				this.receive(todoObj)
				e.target.value = ''
			}
		},
		props:['receive']
	}
</script>

<style scoped>
	.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);
	}
</style>

MyList.vue文件:

<template>
	<ul class="todo-main">
		<!-- 使用props来传递值,父传子 -->
		<Item1 
			v-for="todoObj in todoList" 
			:key="todoObj.id" 
			:itemPro="todoObj" 
			:checkTodoObj="checkTodoObj"
			:deleteTodoObj="deleteTodoObj"
		></Item1>
	</ul>
</template>

<script>
	import Item1 from './MyItem.vue'
	export default {
		name:"MyList",
		components:{Item1},
		props:['todoList','checkTodoObj','deleteTodoObj']
	}
</script>

<style scoped>
	.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>
			<!-- 
				checked勾选该checkbox。
				想要下面的props元素控制checkbox,就可以使用:来使其成为js表达式。这样我们就可以通过js表达式
				来操作了。这里的itemPro我定义为了{id:'001',title:'吃饭',done:true}的结构。
				
				@click和@change都可以操作事件方面的东西。
			-->
			<input type="checkbox" :checked="itemPro.done" @click="handleCheck(itemPro.id)"/>
			<span>{{itemPro.title}}</span>
		</label>
		<button class="btn btn-danger" @click="handleDelete(itemPro.id)">删除</button>
	</li>
</template>

<script>
export default {
	name:"MyItem",
	//声明接受todo对象
	props:['itemPro','checkTodoObj','deleteTodoObj'],
	mounted(){
		// console.log(this.itemPro);
	},
	methods:{
		//勾选or取消
		handleCheck(id){
			//通知App组件将对应的itemPro对象的done值取反
			this.checkTodoObj(id)
		},
		//删除
		handleDelete(id){
			//根据用户
			if(confirm('确定删除吗?')){
				this.deleteTodoObj(id)
			}
		}
	}
}
</script>

<style scoped>
	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>

MyFooter.vue文件:

<template>
	<div class="todo-footer" v-show="total">
		<label>
			<input type="checkbox" :checked="isAll" @change="checkAll"/>
		</label>
		<span>
			<span>已经完成{{doneTotal}} / 全部:{{total}}</span>
		</span>
		<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
	</div>
</template>

<script>
export default {
	name:"MyFooter",
	props:['todoList','checkAllTodoObj','clearAllTodoObj'],
	computed:{
		total(){
			return this.todoList.length
		},
		doneTotal(){
			//reduce的使用:
			//第一个参数是函数,当前todoList的数组长度为多少,就调用多少次。
			//第二个参数是开始的时候的pre的起始值。
			const x = this.todoList.reduce((pre,current)=>{
				//这里的pre参数是上一次执行的返回W值。起始索引是0。
				// console.log('pre参数:',pre)
				//这里的current参数是这次执行的对象。
				// console.log('current参数:',current)
				return pre + (current.done ? 1:0)
			},0)
			
			// console.log("reduce的最终返回:",x)
			return x;
		},
		isAll(){
			return this.doneTotal == this.total && this.total > 0
		}
	},
	methods:{
		checkAll(e){
			// console.log(e.target.checked)
			this.checkAllTodoObj(e.target.checked)
		},
		clearAll(){
			if(confirm("确定清除全部任务吗?"))
				this.clearAllTodoObj()
		}
	}
}
</script>

<style scoped>
	.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>

3. Vue 组件自定义事件


注意:这里是自定义事件,并不是自定义指令,不要搞混了。

vue的组件自定义事件是通过v-on来指定或者@来指定。

3.1 vm. o n 和 v m . on 和 vm. onvm.emit的使用


常见情况:

  • this. r e f s . s t u d e n t . refs.student. refs.student.on(‘事件名’,function(args){ }) :开发中我们使用这种方式来绑定事件。后面的函数我们都叫它回调函数。在这里插入图片描述

  • this.$emit(‘itholmes’,this.name,1,2,3) 触发当前this组件的itholmes事件,并给它传递多个参数。


官方解释:
在这里插入图片描述
在这里插入图片描述

o n 和 on和 onemit一般配合使用的:

  • $on(‘itholmes’,function(){})本质上和下面结构一样。
    在这里插入图片描述
  • 再让$emit(itholmes,args)来触发事件,args是可以传递的参数。
  • 对应的还有/ o n c e ( 一 次 性 ) , / once(一次性),/ once(一次性),/off(解绑)等等。

3.2 两种组件自定义事件绑定的方式(实现子组件向父组件传值)


第一种方式:通过父组件给子组件绑定一个自定义事件实现:子给父传递数据。(使用@或v-on)

第二种方式:通过父组件给子组件绑定一个自定义事件实现:子给父传递数据。(使用ref)

App.vue文件:

<template>
	<div class="app">
		<h1>{{msg}}</h1>
		<!-- 
			这里是给组件Student的实例对象VueComponent绑定了itholmes事件。
			第一种方式:通过父组件给子组件绑定一个自定义事件实现:子给父传递数据。(使用@或v-on)
		-->
		<Student v-on:itholmes.once="getStudentName"></Student>
		<!-- 第二种方式:通过父组件给子组件绑定一个自定义事件实现:子给父传递数据。(使用ref) -->
		<Student ref="student"></Student>
	</div>
</template>

<script>
	import School from "./components/School.vue"
	import Student from "./components/Student.vue"
	export default{
		name:'App',
		components:{Student,School},
		data(){
			return {
				msg:"你好啊!"
			}
		},
		methods:{
			getStudentName(name,...params){
				console.log("App收到了学生名:",name,params)
			}
		},
		mounted(){
			//这种方式相比较上面两种更加灵活一些。
			// this.$refs.student.$on('itholmes',this.getStudentName)//绑定自定义事件
			//也可以用once来操作
			this.$refs.student.$once('itholmes',this.getStudentName)//绑定自定义事件一次性。
		}
	}
</script>

<style>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>

Student.vue文件:

<template>
	<div class="student">
		<h2>学生姓名: {{name}}</h2>
		<h2>学生性别: {{sex}}</h2>
		<button @click="seneStudentName">把学生姓名给App</button>
	</div>
</template>

<script>
	export default{
		name:"Student",
		data(){
			return {
				name:"张三",
				sex:"男"
			}
		},
		methods:{
			seneStudentName(){
				// emit英文直译:发射,排放。
				//$emit来触发Student组件实例身上的itholmes事件,并且将this.name传给itholmes事件中的getStudentName函数。
				this.$emit('itholmes',this.name,1,2,3)
			}
		}
	}
</script>

<style>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

3.3 组件自定义事件 解绑


在这里插入图片描述

解绑事件的操作如下:

  • this.$off(‘itholmes’); 解绑单个事件。

  • this.$off([‘itholmes’,‘demo’]); 解绑多个事件。

  • this.$off(); 解绑所有的自定义事件。

    学生姓名: {{name}}

    学生性别: {{sex}}


当执行$destroy()销毁时,事件会怎么样。
在这里插入图片描述

3.4 组件自定义事件的几个误区

3.4.1 this是父组件还是子组件,如何区分?


注意:使用this r e f s 中 拿 到 的 子 组 件 , 调 用 refs中拿到的子组件,调用 refson或者其他方法中,函数的this指向的是子组件,并不是父组件!

<template>
	<div class="app">
		<h1>{{msg}},学生姓名:{{studentName}}</h1>
		<Student ref="student"></Student>
	</div>
</template>

<script>
	import School from "./components/School.vue"
	import Student from "./components/Student.vue"
	export default{
		name:'App',
		components:{Student,School},
		data(){
			return {
				msg:"你好啊!",
				studentName:""
			}
		},
		methods:{
			getSchoolName(name){
				console.log('App收到了学校名:',name)
			},
			m1(){
				console.log("demo事件被触发了。")
			}
		},
		mounted(){
this.$refs.student.$on('itholmes',function(name,...params){
				console.log("App收到了学生名:",name,params)
				console.log(this)
				//这样做是不可以的!因为这里的this指向的不是当前App组件,而是我们$refs中student组件。
				this.studentName = name
				//因此我们在这里设置name,是设置的student子组件的,并不是App组件!!
				//这个坑要注意!
			})
		}
	}
</script>

<style>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>

这种问题可以通过箭头函数来解决。
在这里插入图片描述
也可以通过在父组件创建methods函数来解决。
在这里插入图片描述

3.4.2 native 修饰符的使用


如果我们想给组件添加原生的一些事件,例如:click事件。组件依然会将它认为是自定义事件!

这个时候就可以使用native修饰符来修饰原生事件,让这整个组件都添加上这个原生事件。

在这里插入图片描述

3.5 自定义事件总结


组件的自定义组件事件是一种组件的通信方式,适用于:子组件 向 父组件发送。

  • 一定注意this的指向问题,是指向的父组件还是子组件,箭头函数下是没有this的要向外查询this。
  • 注意$on的回调函数一般是设置在父组件中。这样也方便父组件接受子组件的传值。

4. 全局事件总线

4.1 全局事件总线的 作用和流程


全局事件总线的作用就是让任意组件之间可以通信。
在这里插入图片描述


Vue与VueComponent的一个重要内置关系:

  • VueComponent.prototype.__proto__ === Vue.prototype 。
    在这里插入图片描述

全局的事件总线就是基于这个规则的。

事件常用的 o n , on, on,emit,$off都是在vue的原型对象上面,就是因为组件和vue原型对象存在一个内置的原型链关系,所以组件才能调用这些方法。

4.2 通过自定义vc组件实例对象 实现全局事件总线(不推荐)


就是通过下面的vc组件的实例对象d实现的。

main.js文件:

import Vue from "vue"
import App from "./App.vue"

Vue.config.productionTip = false;

//这里如果设置了一个Object类型,Object的原型链上面是没有$emit,$on这些东西的。
// Vue.prototype.x = {a:1,b:2}

const Demo = Vue.extend({})
//这里的d就是vc组件的实例对象。
const d = new Demo();

//因此,这里我们想要实现事件总线就必须得设置一个vue实例对象或者vc组件对象
Vue.prototype.x = d;

new Vue({
	el:'#app',
	render:h=>h(App)
})

School.vue组件:

<template>
	<div class="school">
		<h2>学校姓名: {{name}}</h2>
		<h2>学校地址: {{address}}</h2>
	</div>
</template>

<script>
	export default{
		name:"School",
		data(){
			return {
				name:"清华大学",
				address:"北京"
			}
		},
		mounted(){
			// console.log('School',this)
			this.x.$on('hello',(data)=>{
				console.log("我是School组件,收到数据",data)
			})
		}
	}
</script>

<style>
	.school{
		background-color: skyblue;
	}
</style>

Student.vue文件:

<template>
	<div class="student">
		<h2>学生姓名: {{name}}</h2>
		<h2>学生性别: {{sex}}</h2>
		<button @click="sendStudentName">把学生名给School组件</button>
	</div>
</template>

<script>
	export default{
		name:"Student",
		data(){
			return {
				name:"张三",
				sex:"男"
			}
		},
		methods:{
			sendStudentName(){
				this.x.$emit("hello",this.name)
			}
		}
	}
</script>

<style>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

4.3 通过当前的Vue实例对象 实现全局事件总线(推荐)


首先,我们不能写成这种形式。
在这里插入图片描述


我们可以通过钩子函数beforeCreate来操作:

import Vue from "vue"
import App from "./App.vue"

Vue.config.productionTip = false;

new Vue({
	el:'#app',
	render:h=>h(App),
	beforeCreate() {
		//这里的this就是指向的当前vm对象。
		Vue.prototype.x = this; //安装全局事件总线
	}
})

一般安装全局事件总线的名字叫做$bus。

完整代码如下:

import Vue from "vue"
import App from "./App.vue"

Vue.config.productionTip = false;

new Vue({
	el:'#app',
	render:h=>h(App),
	beforeCreate() {
		Vue.prototype.$bus = this;
	}
})

Student.vue文件:

<template>
	<div class="student">
		<h2>学生姓名: {{name}}</h2>
		<h2>学生性别: {{sex}}</h2>
		<button @click="sendStudentName">把学生名给School组件</button>
	</div>
</template>

<script>
	export default{
		name:"Student",
		data(){
			return {
				name:"张三",
				sex:"男"
			}
		},
		methods:{
			sendStudentName(){
				this.$bus.$emit("hello",this.name)
			}
		}
	}
</script>

<style>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

School.vue文件:

<template>
	<div class="school">
		<h2>学校姓名: {{name}}</h2>
		<h2>学校地址: {{address}}</h2>
	</div>
</template>

<script>
	export default{
		name:"School",
		data(){
			return {
				name:"清华大学",
				address:"北京"
			}
		},
		mounted(){
			// console.log('School',this)
			this.$bus.$on('hello',(data)=>{
				console.log("我是School组件,收到数据",data)
			})
		},
		//记得在组件销毁之前,因为我们的这个$bus是一直全局都在的!需要用的就用,不需要的就销毁!
		beforeDestroy(){
			this.$bus.$off("hello")
		}
	}
</script>

<style>
	.school{
		background-color: skyblue;
	}
</style>

4.4 全局事件总线 总结


在这里插入图片描述

5. 对于todoList的全局事件总线修改


main.js文件:

import Vue from "vue"
import App from "./App.vue"

Vue.config.productionTip = false;

new Vue({
	el:'#app',
	render:h=>h(App),
	beforeCreate() {
		//安装事件总线
		Vue.prototype.$bus = this;
	}
})

MyHeader.vue

<template>
	<div class="todo-header">
		<input type="text" placeholder="请输入你的任务名称,按回车确认" @keyup.enter="add">
	</div>
</template>

<script>
	import {nanoid} from 'nanoid'
	export default {
		name:"MyHeader",
		methods:{
			add(e){
				//判断是否为空
				if(!e.target.value.trim())
					return alert('输入不能为空!')
					
				//获取用户输入信息
				console.log(e.target.value)
				
				//包装用户信息,id使用Nanoid来操作
				const todoObj = {
					id:nanoid(),
					title:e.target.value,
					done:false
				}
				
				this.$emit("receive",todoObj)
				
				//输入完成后清空		
				e.target.value = ''
			}
		},
	}
</script>

<style scoped>
	.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);
	}
</style>

MyList.vue文件:

<template>
	<ul class="todo-main">
		<!-- 使用props来传递值,父传子 -->
		<Item1 
			v-for="todoObj in todoList" 
			:key="todoObj.id" 
			:itemPro="todoObj" 
		></Item1>
	</ul>
</template>

<script>
	import Item1 from './MyItem.vue'
	export default {
		name:"MyList",
		components:{Item1},
		props:['todoList']
	}
</script>

<style scoped>
	.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>
			<!-- 
				checked勾选该checkbox。
				想要下面的props元素控制checkbox,就可以使用:来使其成为js表达式。这样我们就可以通过js表达式
				来操作了。这里的itemPro我定义为了{id:'001',title:'吃饭',done:true}的结构。
				
				@click和@change都可以操作事件方面的东西。
			-->
			<input type="checkbox" :checked="itemPro.done" @click="handleCheck(itemPro.id)"/>
			<span>{{itemPro.title}}</span>
		</label>
		<button class="btn btn-danger" @click="handleDelete(itemPro.id)">删除</button>
	</li>
</template>

<script>
export default {
	name:"MyItem",
	//声明接受todo对象
	props:['itemPro'],
	mounted(){
		// console.log(this.itemPro);
	},
	methods:{
		//勾选or取消
		handleCheck(id){
			//通知App组件将对应的itemPro对象的done值取反
			this.$bus.$emit('checkTodoObj',id)
		},
		//删除
		handleDelete(id){
			//根据用户
			if(confirm('确定删除吗?')){
				this.$bus.$emit("deleteTodoObj",id)
			}
		}
	}
}
</script>

<style scoped>
	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>

MyFooter.vue文件:

<template>
	<div class="todo-footer" v-show="total">
		<label>
			<input type="checkbox" :checked="isAll" @change="checkAll"/>
		</label>
		<span>
			<span>已经完成{{doneTotal}} / 全部:{{total}}</span>
		</span>
		<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
	</div>
</template>

<script>
export default {
	name:"MyFooter",
	props:['todoList'],
	computed:{
		total(){
			return this.todoList.length
		},
		doneTotal(){
			//reduce的使用:
			//第一个参数是函数,当前todoList的数组长度为多少,就调用多少次。
			//第二个参数是开始的时候的pre的起始值。
			const x = this.todoList.reduce((pre,current)=>{
				//这里的pre参数是上一次执行的返回W值。起始索引是0。
				// console.log('pre参数:',pre)
				//这里的current参数是这次执行的对象。
				// console.log('current参数:',current)
				return pre + (current.done ? 1:0)
			},0)
			
			// console.log("reduce的最终返回:",x)
			return x;
		},
		isAll(){
			return this.doneTotal == this.total && this.total > 0
		}
	},
	methods:{
		checkAll(e){
			// console.log(e.target.checked)
			this.$emit("checkAllTodoObj",e.target.checked)
		},
		clearAll(){
			if(confirm("确定清除全部任务吗?"))
				this.$emit("clearAllTodoObj")
		}
	}
}
</script>

<style scoped>
	.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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值