当前位置: 首页 > news >正文

跟着尚硅谷学vue2—进阶版2.0—使用 Vue 脚手架2.0

9. 组件的自定义事件

1. 总结
  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){this.$refs.xxx.$on('atguigu',this.test)
      }
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('atguigu',数据)

  5. 解绑自定义事件this.$off('atguigu')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  7. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

2. 案例
1. App.vue
<template><div class="app"><h1>{{ msg }},学生姓名是:{{ studentName }}</h1><!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 --><School :getSchoolName="getSchoolName" /><!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或者v-on)  --><!-- <Student v-on:atguigu="getStudentName" /> --><!-- <Student @atguigu.once="getStudentName" /> --><!-- <Student @atguigu="getStudentName" @demo="m1" /> --><!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref)  --><Student ref="student" @click.native="show" /></div>
</template>
<script>
import Student from "./components/Student.vue";
import School from "./components/School.vue";export default {name: "App",components: { Student, School },data() {return {msg: "你好啊!",studentName: "",};},methods: {getSchoolName(name) {console.log("App收到了学校名:", name);},getStudentName(name, ...params) {console.log("App收到了学生名:", name, params);this.studentName = name;},m1() {console.log("demo时间被触发了");},show() {alert(123);},},mounted() {this.$refs.student.$on("atguigu", this.getStudentName); // 绑定自定义事件  推荐// this.$refs.student.$on("atguigu", (name, ...params) => {//   console.log("App收到了学生名:", name, params);//   console.log(this);//   this.studentName = name;// }); // 绑定自定义事件  不太推荐// setTimeout(()=>{//     this.$refs.student.$on('atguigu',this.getStudentName)// })// this.$refs.student.$once('atguigu',this.getStudentName) // 绑定自定义事件(一次性)},
};
</script>
<style>
.app {background-color: gray;padding: 5px;
}
</style>
2. main.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),// mounted(){//     setTimeout(()=>{//         this.$destroy()//     },3000) // 3秒自动销毁所有实例// },
})
3. School.vue
<template><div class="school"><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2><button @click="sendSchoolName">把学校名给App</button></div>
</template>
<script>
export default {name: 'School',props:['getSchoolName'],data() {return {name: '尚硅谷',address: '北京',}},methods: {sendSchoolName(){this.getSchoolName(this.name)}}
}
</script>
<style scoped>
.school {background-color: skyblue;padding: 5px;
}
</style>
4. Student.vue
<template><div class="student"><h2>学生姓名:{{ name }}</h2><h2>学生性别:{{ sex }}</h2><h2>当前求和为:{{ number }}</h2><button @click="add">点我number++</button><button @click="sendStudentName">把学生名给App</button><button @click="unbind">解绑atguigu事件</button><button @click="death">销毁当前Student组建的实例(vc)</button></div>
</template>
<script>
export default {name: "Student",data() {return {name: "张三",sex: "男",number: 0,};},methods: {add() {console.log("add回调被调用了");this.number++;},sendStudentName() {// 触发Student组件实例身上的atguigu事件this.$emit("atguigu", this.name, 666, 888, 999);// this.$emit("demo");// this.$emit("click");},unbind() {this.$off("atguigu"); // 解绑一个自定义事件// this.$off(["atguigu", "demo"]); // 解绑多个自定义事件// this.$off(); // 解绑所有的自定义事件},death() {this.$destroy(); // 销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全部都不奏效。},},
};
</script>
<style lang="less" scoped>
.student {background-color: pink;padding: 5px;margin-top: 30px;
}
</style>

10. TodoList案例_自定义事件

1. 源码
1. App.vue
<!-- 1.数据在哪里,那么操作数据的方法就在哪里2.props是只读的,不可以做修改-->
<template><div><div class="todo-container"><div class="todo-wrap"><MyHeater @addTodo="addTodo" /><!-- 使用props传参方式改变选中or不选 --><MyList:todos="todos":checkTodo="checkTodo":deleteTodo="deleteTodo"/><!-- 使用v-model方法改变选中or不选 --><!-- <MyList :todos="todos" /> --><MyFooter:todos="todos"@checkAllTodo="checkAllTodo"@clearAllTodo="clearAllTodo"/></div></div></div>
</template>
<script>
import MyList from "./components/MyList";
import MyFooter from "./components/MyFooter";
import MyHeater from "./components/MyHeater.vue";
export default {name: "App",components: { MyHeater, MyList, MyFooter },data() {return {todos: JSON.parse(localStorage.getItem("todos")) || [],};},methods: {// 添加一个todoaddTodo(todoObj) {//   console.log("我是App组件,我收到了数据:", todoObj);this.todos.unshift(todoObj);},// 勾选or取消勾选一个todocheckTodo(id) {this.todos.forEach((todo) => {if (todo.id === id) todo.done = !todo.done;});},// 删除一个tododeleteTodo(id) {this.todos = this.todos.filter((todo) => todo.id !== id);},// 全选 or 取消全选checkAllTodo(done) {this.todos.forEach((todo) => {todo.done = done;});},// 清除所有已完成的todoclearAllTodo() {this.todos = this.todos.filter((todo) => {return !todo.done;});},},watch: {todos: {deep:true,handler(value){localStorage.setItem("todos", JSON.stringify(value));}},},
};
</script>
<style>
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>
2. MyHeater.vue
<template><div class="todo-header"><inputtype="text"placeholder="请输入你的任务名称,按回车键确认"v-model="title"@keyup.enter="add"/></div>
</template><script>
import { nanoid } from "nanoid";
export default {name: "MyHeater",props: ["addTodo"],data() {return {title: "",};},methods: {add() {// 校验数据if (!this.title.trim()) return alert("输入不能为空");// 将用户的输入包装成一个todo对象// console.log(e.target.value);const todoObj = { id: nanoid(), title: this.title, done: false };// console.log(todoObj);// 通知App组件添加一个todo对象// this.addTodo(todoObj);this.$emit("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>
3. 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() {// let i = 0;// this.todos.forEach((todo) => {//   if (todo.done) i++;// });// return i;/*** 第一次统计的初始值是0,所以pre是0* 第二次调用函数((pre, current) => {console.log("@", pre);})的时候 pre是第一次调用的函数的返回值(如果没有设置返回值,则返回undefined)* 第三次、第四次。。。。pre都是上次的* 最后一次调用函数 ((pre, current) => {console.log("@", pre);})的返回值就是reduce的返回值*/// const x = this.todos.reduce((pre, current) => {//   console.log("@", pre, current);//   return pre + 1;// }, 0);// console.log("###", x);// const x = this.todos.reduce((pre, current) => {//   console.log("@", pre, current);//   return pre + (current.done ? 1 : 0);// }, 0);// console.log("###", x);// 简洁化return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);},// 只被读取,不被修改// isAll() {//   return this.doneTotal === this.total && this.total > 0;// },// 想要被修改,要写完整isAll: {get() {return this.doneTotal === this.total && this.total > 0;},set(value) {// this.checkAllTodo(value);this.$emit("checkAllTodo", value);},},},methods: {// 使用props方法进行全选反选// checkAll(e) {//   // console.log(e.target.checked);//   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>

11. 全局事件总线(GlobalEventBus)

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

    1. 包含事件处理相关方法的对象(只有一个)
    2. 所有的组件都可以得到
  2. 所有组件实例对象的原型对象的原型对象就是 Vue 的原型对象

    1. 所有组件对象都能看到 Vue 原型对象上的属性和方法
    2. Vue.prototype.$bus = new Vue(), 所有的组件对象都能看到$bus 这个属性对象
  3. 安装全局事件总线:

    new Vue({......beforeCreate() { // 尽量早的执行挂载全局事件总线对象的操作Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm},......
    }) 
  4. 使用事件总线:

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

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

    3. 绑定事件

      this.$globalEventBus.$on('deleteTodo', this.deleteTodo)
    4. 分发事件

      this.$globalEventBus.$emit('deleteTodo', this.index) 
    5. 解绑事件

      this.$globalEventBus.$off('deleteTodo')  
  5. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

  6. Vue 原型对象上包含事件处理的方法

    1. $on(eventName, listener) : 绑定自定义事件监听
    2. $emit(eventName, data): 分发自定义事件
    3. $off(eventName): 解绑自定义事件监听
    4. $once(eventName, listener): 绑定事件监听, 但只能处理一次

全局事件总线:任意组件间通信

2. 案例
1. App.vue
<template><div class="app"><h1>{{ msg }}</h1><School /><Student /></div>
</template>
<script>
import Student from "./components/Student.vue";
import School from "./components/School.vue";export default {name: "App",components: { Student, School },data() {return {msg: "你好啊!",};},
};
</script>
<style>
.app {background-color: gray;padding: 5px;
}
</style>
2. main.js
// 引入Vue
import Vue from "vue";
// 引入App
import App from "./App.vue";
// 关闭Vue的生产提示
Vue.config.productionTip = false;// 使用vc进行任意组件间通信
/* const Demo = Vue.extend({});
const d = new Demo();
Vue.prototype.x = d; */// 使用vm进行任意组件间通信
// 创建vm
new Vue({el: "#app",render: (h) => h(App),beforeCreate() {Vue.prototype.$bus = this; // 安装全局事件总线(最标准的写法)},
});
3. 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.$on);console.log("School", this.$bus);this.$bus.$on("hello", (data) => {console.log("我是School组件,收到了数据", data);});},beforeDestroy() {this.$bus.$off("hello");},
};
</script>
<style scoped>
.school {background-color: skyblue;padding: 5px;
}
</style>
4. 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: "男",};},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>

12. TodoList案例_全局事件总线

1. 源码
1. App.vue
<!-- 1.数据在哪里,那么操作数据的方法就在哪里2.props是只读的,不可以做修改-->
<template><div><div class="todo-container"><div class="todo-wrap"><MyHeater @addTodo="addTodo" /><!-- 使用props传参方式改变选中or不选 --><MyList :todos="todos" /><!-- 使用v-model方法改变选中or不选 --><!-- <MyList :todos="todos" /> --><MyFooter:todos="todos"@checkAllTodo="checkAllTodo"@clearAllTodo="clearAllTodo"/></div></div></div>
</template>
<script>
import MyList from "./components/MyList";
import MyFooter from "./components/MyFooter";
import MyHeater from "./components/MyHeater.vue";
export default {name: "App",components: { MyHeater, MyList, MyFooter },data() {return {todos: JSON.parse(localStorage.getItem("todos")) || [],};},methods: {// 添加一个todoaddTodo(todoObj) {//   console.log("我是App组件,我收到了数据:", todoObj);this.todos.unshift(todoObj);},// 勾选or取消勾选一个todocheckTodo(id) {this.todos.forEach((todo) => {if (todo.id === id) todo.done = !todo.done;});},// 删除一个tododeleteTodo(id) {this.todos = this.todos.filter((todo) => todo.id !== id);},// 全选 or 取消全选checkAllTodo(done) {this.todos.forEach((todo) => {todo.done = done;});},// 清除所有已完成的todoclearAllTodo() {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>
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>
2. main.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;},
});
3. MyList.vue
<template><ul class="todo-main"><!-- 使用props传参方式改变选中or不选 --><MyItemv-for="todoObj in todos":key="todoObj.id":todo="todoObj"/><!-- 使用v-model方法改变选中or不选 --><!-- <MyItemv-for="todoObj in todos":key="todoObj.id":todo="todoObj"/> --></ul>
</template><script scoped>
import MyItem from "./MyItem";
export default {name: "MyList",components: { MyItem },// 声明接收App传递过来的数据props: ["todos"],data() {return {};},
};
</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>
4. MyItem.vue
<template><div><li><label><!-- 使用props传参方式改变选中or不选 --><!-- <inputtype="checkbox":checked="todo.done"@click="handleCheck(todo.id)"/> --><inputtype="checkbox":checked="todo.done"@change="handleCheck(todo.id)"/><!-- 使用v-model方法改变选中or不选 --><!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props,只不过vue没检测到 --><!-- <input type="checkbox" v-model="todo.done" /> --><span>{{ todo.title }}</span></label><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button></li></div>
</template><script scoped>
export default {name: "MyItem",// 声明接受todo对象props: ["todo"],methods: {// 勾选or取消勾选handleCheck(id) {// console.log(id);// 通知App组件将对用的todo对象的done值取反// this.checkTodo(id);this.$bus.$emit("checkTodo", id);},// 删除handleDelete(id) {if (confirm("确定删除吗?")) {// 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>

13. 消息订阅与发布(pubsub)

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

  2. 使用步骤:

    1. 在线文档: https://github.com/mroderick/PubSubJS

    2. 安装pubsub:npm i pubsub-js

    3. 引入: import pubsub from 'pubsub-js'

    4. 相关语法

      1. import PubSub from 'pubsub-js' // 引入
      2. PubSub.subscribe(‘msgName’, functon(msgName, data){ })
      3. PubSub.publish(‘msgName’, data): 发布消息, 触发订阅的回调函数调用
      4. PubSub.unsubscribe(token): 取消消息的订阅
    5. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

      methods(){demo(data){......}
      }
      ......
      mounted() {this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
    6. 提供数据:pubsub.publish('xxx',数据)

    7. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。

    8. 原生js无法完成发布订阅操作,要借助插件,推荐:pubsub-js

    消息订阅与发布

2. 案例
1. App.vue
<template><div class="app"><h1>{{ msg }}</h1><School /><Student /></div>
</template>
<script>
import Student from "./components/Student.vue";
import School from "./components/School.vue";export default {name: "App",components: { Student, School },data() {return {msg: "你好啊!",};},
};
</script>
<style>
.app {background-color: gray;padding: 5px;
}
</style>
2. School.vue
<template><div class="school"><h2>学校名称:{{ name }}</h2><h2>学校地址:{{ address }}</h2></div>
</template>
<script>
import pubsub from "pubsub-js";
export default {name: "School",data() {return {name: "尚硅谷",address: "北京",};},methods: {demo(msgName, data) {console.log("有人发布了hello消息,hello消息的回调执行了", msgName, data);},},mounted() {// console.log("School", this.$on);// console.log("School", this.$bus);// this.$bus.$on("hello", (data) => {//   console.log("我是School组件,收到了数据", data);// });// 订阅消息 subscribe// this.pubId = pubsub.subscribe("hello", (msgName, data) => {//   console.log(this);//   console.log("有人发布了hello消息,hello消息的回调执行了", msgName, data);// });this.pubId = pubsub.subscribe("hello", this.demo);},beforeDestroy() {// this.$bus.$off("hello");pubsub.unsubscribe(this.pubId); // 取消订阅 unsubscribe},
};
</script>
<style scoped>
.school {background-color: skyblue;padding: 5px;
}
</style>
3. Student.vue
<template><div class="student"><h2>学生姓名:{{ name }}</h2><h2>学生性别:{{ sex }}</h2><button @click="sendStudentName">把学生名给School组件</button></div>
</template>
<script>
import pubsub from "pubsub-js";
export default {name: "Student",data() {return {name: "张三",sex: "男",};},mounted() {// console.log("Student", this.x);},methods: {sendStudentName() {// this.$bus.$emit("hello", this.name);pubsub.publish("hello", 666);},},
};
</script>
<style lang="less" scoped>
.student {background-color: pink;padding: 5px;margin-top: 30px;
}
</style>

14. TodoList案例_pubsub

1. 源码
1. App.vue
<!-- 1.数据在哪里,那么操作数据的方法就在哪里2.props是只读的,不可以做修改-->
<template><div><div class="todo-container"><div class="todo-wrap"><MyHeater @addTodo="addTodo" /><!-- 使用props传参方式改变选中or不选 --><MyList :todos="todos" /><!-- 使用v-model方法改变选中or不选 --><!-- <MyList :todos="todos" /> --><MyFooter:todos="todos"@checkAllTodo="checkAllTodo"@clearAllTodo="clearAllTodo"/></div></div></div>
</template>
<script>
import pubsub from "pubsub-js";
import MyList from "./components/MyList";
import MyFooter from "./components/MyFooter";
import MyHeater from "./components/MyHeater.vue";
export default {name: "App",components: { MyHeater, MyList, MyFooter },data() {return {todos: JSON.parse(localStorage.getItem("todos")) || [],};},methods: {// 添加一个todoaddTodo(todoObj) {//   console.log("我是App组件,我收到了数据:", todoObj);this.todos.unshift(todoObj);},// 勾选or取消勾选一个todocheckTodo(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;});},// 清除所有已完成的todoclearAllTodo() {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.pubId = pubsub.subscribe("deleteTodo", this.deleteTodo);},beforeDestroy() {this.$bus.$off("checkTodo");pubsub.unsubscribe(this.pubId); // 取消订阅 unsubscribe},
};
</script>
<style>
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>
2. MyItem.vue
<template><div><li><label><!-- 使用props传参方式改变选中or不选 --><!-- <inputtype="checkbox":checked="todo.done"@click="handleCheck(todo.id)"/> --><inputtype="checkbox":checked="todo.done"@change="handleCheck(todo.id)"/><!-- 使用v-model方法改变选中or不选 --><!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props,只不过vue没检测到 --><!-- <input type="checkbox" v-model="todo.done" /> --><span>{{ todo.title }}</span></label><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button></li></div>
</template><script scoped>
import pubsub from "pubsub-js";
export default {name: "MyItem",// 声明接受todo对象props: ["todo"],methods: {// 勾选or取消勾选handleCheck(id) {// console.log(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);}},},
};
</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>

15. TodoList案例_编辑

1. 源码
1. App.vue
<!-- 1.数据在哪里,那么操作数据的方法就在哪里2.props是只读的,不可以做修改-->
<template><div><div class="todo-container"><div class="todo-wrap"><MyHeater @addTodo="addTodo" /><!-- 使用props传参方式改变选中or不选 --><MyList :todos="todos" /><!-- 使用v-model方法改变选中or不选 --><!-- <MyList :todos="todos" /> --><MyFooter:todos="todos"@checkAllTodo="checkAllTodo"@clearAllTodo="clearAllTodo"/></div></div></div>
</template>
<script>
import pubsub from "pubsub-js";
import MyList from "./components/MyList";
import MyFooter from "./components/MyFooter";
import MyHeater from "./components/MyHeater.vue";
export default {name: "App",components: { MyHeater, MyList, MyFooter },data() {return {todos: JSON.parse(localStorage.getItem("todos")) || [],};},methods: {// 添加一个todoaddTodo(todoObj) {//   console.log("我是App组件,我收到了数据:", todoObj);this.todos.unshift(todoObj);},// 勾选or取消勾选一个todocheckTodo(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;});},// 清除所有已完成的todoclearAllTodo() {this.todos = this.todos.filter((todo) => {return !todo.done;});},// 修改updateTodo(id, title) {this.todos.forEach((todo) => {if (todo.id === id) todo.title = title;});},},watch: {todos: {deep: true,handler(value) {localStorage.setItem("todos", JSON.stringify(value));},},},mounted() {this.$bus.$on("checkTodo", this.checkTodo);this.$bus.$on("updateTodo", this.updateTodo);this.pubId = pubsub.subscribe("deleteTodo", this.deleteTodo);},beforeDestroy() {this.$bus.$off("checkTodo");this.$bus.$off("updateTodo");pubsub.unsubscribe(this.pubId); // 取消订阅 unsubscribe},
};
</script>
<style>
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-edit {color: #fff;background-color: skyblue;border: 1px solid rgb(103, 159, 180);margin-right: 5px;
}
.btn-edit:hover {color: #fff;background-color: skyblue;
}.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>
2. MyItem.vue
<template><div><li><label><!-- 使用props传参方式改变选中or不选 --><!-- <inputtype="checkbox":checked="todo.done"@click="handleCheck(todo.id)"/> --><inputtype="checkbox":checked="todo.done"@change="handleCheck(todo.id)"/><!-- 使用v-model方法改变选中or不选 --><!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props,只不过vue没检测到 --><!-- <input type="checkbox" v-model="todo.done" /> --><span v-show="!todo.isEdit">{{ todo.title }}</span><inputv-show="todo.isEdit"type="text":value="todo.title"@blur="handleBlur(todo, $event)"/></label><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button><buttonv-show="!todo.isEdit"class="btn btn-edit"@click="handleEdit(todo)">编辑</button></li></div>
</template><script scoped>
import pubsub from "pubsub-js";
export default {name: "MyItem",// 声明接受todo对象props: ["todo"],methods: {// 勾选or取消勾选handleCheck(id) {// console.log(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) {if (todo.hasOwnProperty.call("isEdit")) {console.log("如果todo身上有isEdit");todo.isEdit = true;} else {console.log("如果todo身上没有isEdit");this.$set(todo, "isEdit", true);}},// 失去焦点回调(真正智兴修改逻辑handleBlur(todo, e) {todo.isEdit = false;if (!e.target.value.trim()) return alert("输入不能为空");console.log("updateTodo", todo.id, e.target.value);this.$bus.$emit("updateTodo", todo.id, e.target.value);},},
};
</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>
2.补充

解决错误:Do not access Object.prototype method ‘hasOwnProperty‘ from target object no-prototype-builtins_爱学习的廖某的博客-CSDN博客


http://www.mrgr.cn/news/71978.html

相关文章:

  • hive的tblproperties支持修改的属性
  • PostgreSQL 页损坏如何修复
  • WordPress 2024主题实例镜像
  • 前端跨域~简述
  • pycharm报错:no module named cv2.cv2
  • 递归探秘:从斐波那契数列到迷宫求解
  • 常用数字器件的描述-时序逻辑器件的描述
  • 类似keepalived的软件还有哪些
  • Docker部署Redis哨兵
  • 在 Service Worker 中caches.put() 和 caches.add()/caches.addAll() 方法他们之间的区别
  • 【知识科普】ARM架构和x86架构
  • CustomersettleController
  • 大循环引起CPU负载过高
  • Android命令行启动SoftAP功能
  • golang项目三层依赖架构,自底向上;依赖注入trpc\grpc
  • 51c视觉~合集6
  • 【含文档】基于ssm+jsp的在线网课管理系统(含源码+数据库+lw)
  • 音视频入门基础:MPEG2-TS专题(3)——TS Header简介
  • 解剖C++模板(2) —— 模板匹配规则及特化
  • 面向对象试题答案
  • 【Python爬虫实战】轻量级爬虫利器:DrissionPage之SessionPage与WebPage模块详解
  • 斯坦福泡茶机器人DexCap源码解析:涵盖收集数据、处理数据、模型训练三大阶段
  • MATLAB基础应用精讲-【数模应用】Google Caffeine算法
  • Linux设置socks代理
  • Mapwindow5代码BUG记录1
  • AI与育儿领域的融合——探索未来的可能性