初学Vue
todoList案例进阶版-组件自定义事件实现组件间通信
简介
其他相关文章
使用组件自定义事件实现组件间通信时并不需要使用props接收数据,通过$emit可以直接操作父组件中组件标签绑定的自定义事件并实现数据传递,从而实现子组件数据传递给父组件。
有如下几个组件,父组件App,其下有子组件MyHeader.vue,MyList.vue,MyItem.vue,MyFooter.vue
页面效果
具体代码
- App.vue
<template>
<div id="app">
<div class="todo-container">
<div class="todo-wrap">
<!-- 输入框部分 -->
<!-- 给组件标签绑定自定义事件-->
<MyHeader @addTodo="addTodo"></MyHeader>
<!-- /输入框部分 -->
<!-- 列表部分 -->
<MyList
:todos="todos"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
></MyList>
<!-- /列表部分 -->
<!-- 底部部分 -->
<MyFooter
:todos="todos"
@checkAllTodo="checkAllTodo"
@clearAllTodo="clearAllTodo"
></MyFooter>
<!-- /底部部分 -->
</div>
</div>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
name: "App",
components: {
MyHeader,
MyFooter,
MyList,
},
data() {
return {
todos:JSON.parse(localStorage.getItem("todos")) || [],
};
},
methods:{
// 添加一个列表事项
addTodo(e){
// console.log("我是App组件,我收到了数据",x)
this.todos.unshift(e)
},
// 勾选or取消勾选todo事项
checkTodo(id){
// 拿到todos中的每一项,找到对应id的那一项
this.todos.forEach((todo)=>{
if(todo.id === id)
todo.done = !todo.done
})
},
// 点击删除一个todo
deleteTodo(id){
this.todos = this.todos.filter((todo)=>{
return todo.id !== id
})
},
// 控制全选或全不选
checkAllTodo(done){
this.todos.forEach((todo)=>{
todo.done = done;
})
},
// 清除已经完成的事项
clearAllTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
}
},
watch:{
// 监视todos,当todos发生改变时,触发
todos:{
handler(value){
localStorage.setItem("todos",JSON.stringify(value))
}
}
}
}
</script>
<style>
/* 整体样式 */
body {
background: rgba(230, 241, 245, 0.816);
}
.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>
- Header.vue
<template>
<div>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,回车键确认"
@keyup.enter="add"
/>
</div>
</div>
</template>
<script>
import {
nanoid} from "nanoid"
export default {
name: "MyHeader",
methods:{
add(event){
// 如果输入为空,则结束方法,不添加任何数据
if(!event.target.value){
return alert("输入不能为空")
}
// 使用能生成唯一标识的库nanoid或者uuid生成id,这里用nanoid(轻量级的uuid)
// 将用户的输入包装成一个todo对象
const todoObj = {
id:nanoid(),title:event.target.value,done:false}
// 通过$emit操作父组件组件标签上绑定的事件,并传入一个参数todoObj
this.$emit("addTodo",todoObj)
event.target.value='' // 回车后清空输入框中的内容
}
},
};
</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>
- MyList.vue
<template>
<div>
<ul ul="todo-main">
<!-- 显示todos数组中所有的事项,用id作为key -->
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
></MyItem>
</ul>
</div>
</template>
<script>
import MyItem from "./MyItem.vue";
export default {
name: "MyList",
components: {
MyItem },
props:['todos','checkTodo','deleteTodo']
};
</script>
<style scoped>
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
</style>
- MyItem.vue
<template>
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<span>{
{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</div>
</template>
<script>
export default {
name: "MyItem",
props:['todo','checkTodo','deleteTodo'], // 这里todo是个对象,由MyList传过来的,存储了id、title、done属性
methods:{
handleCheck(id){
// 通知App组件将对应todo对象的done值取反
this.checkTodo(id)
},
// 删除事项
handleDelete(id){
if(confirm("确定删除吗?")){
// 通知App删除数据
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:hover {
background-color: rgb(226, 219, 219);
cursor: pointer;
}
li:hover button{
display:block
}
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>
- MyFooter.vue
<template>
<div>
<!--当列表中没有事项时,v-show用于隐藏Footer部分-->
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" :checked="isAll" @change="checkAll"/>
</label>
<span> <span>已完成{
{doneTotal}}</span>/ 全部{
{total}}</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</div>
</template>
<script>
export default {
name: "MyFooter",
props:['todos'],
computed:{
total(){
return this.todos.length
},
doneTotal(){
// 高端一点的方法
// return this.todos.reduce((pre,current)=>{
// return pre + (current.done ? 1 : 0)
// },0)
// 普通的方法
let i=0
this.todos.forEach((todo)=>{
if(todo.done) i++
})
return i
},
isAll:{
// 看全选框是否勾选
get(){
return this.doneTotal === this.total && this.total>0
},
// isAll被修改时set被调用
set(value){
// this.checkAllTodo(value)
this.$emit("checkAllTodo",value);
}
}
},
methods:{
// 全选或取消全选
checkAll(event){
// this.checkAllTodo(event.target.checked)
this.$emit("checkAllTodo",event.target.checked)
},
// 清除已完成事项
// clearAll(){
// this.clearAllTodo()
// }
clearAll(){
this.$emit('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>
说明:App.vue和myList.vue、MyItem.vue组件间的关系是“爷父孙”的关系,使用自定义事件实现组件间通信是不可能的,因为myItem.vue中的数据要通过myList间接传递给App.vue,中间必须借助props实现。