14. 全局事件总线实现组件数据传递
全局事件总线实现TodoItem组件向App组件传递数据。
main.js
import Vue from 'vue' import App from './App.vue' //关闭vue的生产提示 Vue.config.productionTip = false new Vue({ render: h => h(App), beforeCreate() { // 注册全局事件总线 Vue.prototype.$bus = this } }).$mount('#app')
App.vue
<template> <div class="todo-container"> <div class="todo-wrap"> <!-- 为子组件TodoAddTask绑定自定义事件addTodo,事件的回调函数为addTodo() --> <TodoAddTask @addTodo="addTodo"></TodoAddTask> <!-- 将数据传入子组件 --> <TodoList :todos="todos"></TodoList> <!-- 为子组件 TodoSituation 绑定自定义事件checkAll deleteDone --> <TodoSituation :todos="todos" @checkAll="checkAll" @deleteDone="deleteDone"></TodoSituation> </div> </div> </template> <script> // 导入子组件 import TodoAddTask from './components/TodoAddTask.vue' import TodoList from './components/TodoList.vue' import TodoSituation from './components/TodoSituation.vue' export default { name: 'App', components: { TodoAddTask, TodoList, TodoSituation }, data() { return { todos: JSON.parse(localStorage.getItem('todos')) || [] } }, methods: { // 将新的待做事项添加到列表中 addTodo(todo) { this.todos.unshift(todo) }, // 修改待做事项的勾选状态 changeDone(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 }) }, // 全选 全不选 checkAll(done) { this.todos.forEach(todo => { todo.done = done }) }, // 清除已完成 deleteDone() { this.todos = this.todos.filter(todo => !todo.done) } }, watch: { // 使用监视属性监视todos的改变 // 只要todos发生了改变,就将新的todos进行本地存储 todos: { // 由于默认只会监视一层,监视数组内对象中是否完成的变化需要进行深度监视 deep: true, handler(newVal) { // 由于todos是数组对象类型数据,进行本地存储需要转换为json localStorage.setItem('todos', JSON.stringify(newVal)) } } }, mounted() { // 为全局事件总线绑定自定义事件,用于组件之间的数据通信 this.$bus.$on('changeDone', this.changeDone) this.$bus.$on('deleteTodo', this.deleteTodo) }, beforeDestroy() { // 为全局事件总线解绑自定义事件 this.$bus.$off('changeDone') 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>
TodoList.vue
<template> <ul class="todo-main"> <!-- 将每个待做事项传入TodoItem --> <TodoItem v-for="todo in todos" :key="todo.id" :todoObj="todo" ></TodoItem> </ul> </template> <script> // 导入子组件 import TodoItem from './TodoItem.vue' export default { name: 'TodoList', components: {TodoItem}, 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>
TodoItem.vue
<template> <li> <label> <!-- 使用v-bind绑定checked属性,true则有这个属性,false无这个属性 --> <input type="checkbox" :checked="todoObj.done" @click="changeChecked"/> <span>{{todoObj.todo}}</span> </label> <button class="btn btn-danger" @click="handlerDelete">删除</button> </li> </template> <script> export default { name: 'TodoItem', props: ['todoObj'], methods: { // 修改勾选状态 changeChecked() { // this.changeDone(this.todoObj.id) // 触发自定义事件向App组件传递修改后的数据 this.$bus.$emit('changeDone', this.todoObj.id) }, // 删除 handlerDelete() { // 弹出框点击确定执行if中的代码 // 点击取消不执行 if (confirm('确定删除吗?')) { // this.deleteTodo(this.todoObj.id) // 触发自定义事件向App组件传递修改后的数据 this.$bus.$emit('deleteTodo', this.todoObj.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: inline-block; } </style>
15. 实现待做事项的修改
TodoItem.vue
<template> <li> <label> <!-- 使用v-bind绑定checked属性,true则有这个属性,false无这个属性 --> <input type="checkbox" :checked="todoObj.done" @click="changeChecked" /> <span v-show="!todoObj.isEdit">{{todoObj.todo}}</span> <!-- ref="inputTask" 获取输入框 --> <input v-show="todoObj.isEdit" type="text" v-model="todoObj.todo" @blur="handlerBlur" ref="inputTask"> </label> <button class="btn btn-danger" @click="handlerDelete">删除</button> <!-- 输入框出现隐藏按钮 --> <button v-show="!todoObj.isEdit" class="btn btn-edit" @click="handlerEdit">编辑</button> </li> </template> <script> export default { name: 'TodoItem', props: ['todoObj'], methods: { // 修改勾选状态 changeChecked() { // this.changeDone(this.todoObj.id) // 触发自定义事件向App组件传递修改后的数据 this.$bus.$emit('changeDone', this.todoObj.id) }, // 删除 handlerDelete() { // 弹出框点击确定执行if中的代码 // 点击取消不执行 if (confirm('确定删除吗?')) { // this.deleteTodo(this.todoObj.id) // 触发自定义事件向App组件传递修改后的数据 this.$bus.$emit('deleteTodo', this.todoObj.id) } }, // 编辑待做事项 handlerEdit() { // 判断对象上是否已经存在isEdit属性 // 存在修改isEdit if ('isEdit' in this.todoObj) { console.log('todoObj存在isEdit属性,进行属性值的修改') this.todoObj.isEdit = true } else { // 向待做事项对象上添加是否处于编辑状态 // 初始为编辑状态 console.log('todoObj没有isEdit属性,进行属性的添加') this.$set(this.todoObj, 'isEdit', true) } // nextTick方法会在模板解析完成后执行回调函数中的代码 this.$nextTick(() => { // 获取输入框自动获取焦点 this.$refs.inputTask.focus() // <input v-show="todoObj.isEdit" type="text" v-model="todoObj.todo" @blur="handlerBlur" ref="inputTask"> 双向数据绑定,直接修改对象内的数值,不用在另外进行值的更新 }) }, // 失去焦点取消编辑状态 handlerBlur() { this.todoObj.isEdit = false } } } </script>