VUE学习(十六)、全局事件总线及TodoList案例事件总线实现

VUE(十六)、全局事件总线及TodoList案例事件总线实现

全局事件总线(GlobalEventBus)

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 安装全局事件总线:

new Vue({
	......
	beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
	},
    ......
}) 
  1. 使用事件总线:

  2. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

    methods(){
      demo(data){......}
    }
    ......
    mounted() {
      this.$bus.$on('xxxx',this.demo)
    }
    
  3. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

1、父给子传 props、全局事件总线(没必要)

2、子给父传 props(父亲先给儿子一个函数,儿子调用这个函数)、自定义事件、全局总线(没必要)

3、多层传递 适合用全局事件总线

思想:创建一个用于全局可访问的组件(没有其他作用,仅作为数据传递),再将VUE原型上添加一个值为这个组件对象,一般称之为$bus

1、父给子传 props、全局事件总线(没必要)

2、子给父传 props(父亲先给儿子一个函数,儿子调用这个函数)、自定义事件、全局总线(没必要)

3、多层传递 适合用全局事件总线

思想:创建一个用于全局可访问的组件(没有其他作用,仅作为数据传递),再将VUE原型上添加一个值为这个组件对象,一般称之为$bus

1、main.js

//引入Vue
import Vue from "vue";
//引入App
import App from "./App.vue";
//关闭Vue的生产提示
Vue.config.productionTip = false;

/* 
	创建一个空vc实例对象
	const myvc = Vue.extend({});
	// 初始化一个全局vc实列对象
	const GlobalVC = new myvc();
	// 将这个vc实例对象挂在到vue原型上
	Vue.prototype.$bus = GlobalVC; 
*/
//创建vm
new Vue({
    el: "#app",
    render: (h) => h(App),
    beforeCreate() {
        // bus(总线、公交车。。。。)语义形象
        Vue.prototype.$bus = this; //Vue原型安装全局事件总线
    }
});

2、使用

绑定

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

<script>
	export default {
		name:'School',
		data() {
			return {
				name:'xxx大学',
				address:'北京',
			}
		},
		mounted() {
			// console.log('School',this)
			// console.log(this.gvc)
			// 绑定一个hello事件
			this.$bus.$on('hello',(data)=>{
				console.log('我是School组件,收到了数据',data)
			})
		},
		beforeDestroy() {
			this.$bus.$off('hello')
		},
	}
</script>

<style scoped>
	.school{
		background-color: skyblue;
		padding: 5px;
	}
</style>

触发

<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:'男',
			}
		},
		mounted() {
			// console.log('Student',this.x)
		},
		methods: {
			sendStudentName(){
				this.$bus.$emit('hello',this.name)
			}
		},
	}
</script>

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

App.vue

<template>
    <div class="app">
        <h1>{{ msg }}</h1>
        <School />
        <Student />
    </div>
</template>

<script>
import Student from "./components/Student";
import School from "./components/School";

export default {
    name: "App",
    components: { School, Student },
    data() {
        return {
            msg: "你好啊!"
        };
    }
};
</script>

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

3、TodoList案例事件总线实现

--------------------------------

①、mian.js

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false

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

②、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"],
    computed: {
        //总数
        total() {
            return this.todos.length;
        },
        //已完成数
        doneTotal() {
            //此处使用reduce方法做条件统计
            /* const x = this.todos.reduce((pre,current)=>{
					console.log('@',pre,current)
					return pre + (current.done ? 1 : 0)
				},0) */
            //简写
            return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);
        },
        //控制全选框
        isAll: {
            //全选框是否勾选
            get() {
                return this.doneTotal === this.total && this.total > 0;
            },
            //isAll被修改时set被调用
            set(value) {
                // this.checkAllTodo(value)
                this.$emit("checkAllTodo", value);
            }
        }
    },
    methods: {
        /* checkAll(e){
				this.checkAllTodo(e.target.checked)
			} */
        //清空所有已完成
        clearAll() {
            // this.clearAllTodo()
            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>

③、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",
    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.$emit("addTodo", todoObj, 1, 2, 3);
            //清空输入
            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>

④、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
    props: ["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);
            }
        }
    }
};
</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>

⑤、MyList.vue

<template>
    <ul class="todo-main">
        <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" />
    </ul>
</template>

<script>
import MyItem from "./MyItem";

export default {
    name: "MyList",
    components: { MyItem },
    //声明接收App传递过来的数据
    props: ["todos"]
};
</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>

⑥、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 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: JSON.parse(localStorage.getItem("todos")) || []
        };
    },
    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;
            });
        }
    },
    watch: {
        todos: {
            deep: true,
            handler(value) {
                localStorage.setItem("todos", JSON.stringify(value));
            }
        }
    },
    mounted() {
        this.$bus.$on("checkTodo", this.checkTodo);
        this.$bus.$on("deleteTodo", this.deleteTodo);
    },
    beforeDestroy() {
        this.$bus.$off("checkTodo");
        this.$bus.$off("deleteTodo");
    }
};
</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>

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;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心醉瑶瑾前

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值