跟着尚硅谷学vue2—进阶版2.0—使用 Vue 脚手架2.0
9. 组件的自定义事件
1. 总结
-
一种组件间通信的方式,适用于:子组件 ===> 父组件
-
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
-
绑定自定义事件:
-
第一种方式,在父组件中:
<Demo @atguigu="test"/>
或<Demo v-on:atguigu="test"/>
-
第二种方式,在父组件中:
<Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('atguigu',this.test) }
-
若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法。
-
-
触发自定义事件:
this.$emit('atguigu',数据)
-
解绑自定义事件
this.$off('atguigu')
-
组件上也可以绑定原生DOM事件,需要使用
native
修饰符。 -
注意:通过
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: {
// 添加一个todo
addTodo(todoObj) {
// console.log("我是App组件,我收到了数据:", 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));
}
},
},
};
</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">
<input
type="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. 总结
-
一种组件间通信的方式,适用于任意组件间通信。
- 包含事件处理相关方法的对象(只有一个)
- 所有的组件都可以得到
-
所有组件实例对象的原型对象的原型对象就是 Vue 的原型对象
- 所有组件对象都能看到 Vue 原型对象上的属性和方法
Vue.prototype.$bus = new Vue()
, 所有的组件对象都能看到$bus 这个属性对象
-
安装全局事件总线:
new Vue({ ...... beforeCreate() { // 尽量早的执行挂载全局事件总线对象的操作 Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ...... })
-
使用事件总线:
-
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) }
-
提供数据:
this.$bus.$emit('xxxx',数据)
-
绑定事件
this.$globalEventBus.$on('deleteTodo', this.deleteTodo)
-
分发事件
this.$globalEventBus.$emit('deleteTodo', this.index)
-
解绑事件
this.$globalEventBus.$off('deleteTodo')
-
-
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
-
Vue 原型对象上包含事件处理的方法
$on(eventName, listener)
: 绑定自定义事件监听$emit(eventName, data)
: 分发自定义事件$off(eventName)
: 解绑自定义事件监听$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: {
// 添加一个todo
addTodo(todoObj) {
// console.log("我是App组件,我收到了数据:", 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>
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不选 -->
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
/>
<!-- 使用v-model方法改变选中or不选 -->
<!-- <MyItem
v-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不选 -->
<!-- <input
type="checkbox"
:checked="todo.done"
@click="handleCheck(todo.id)"
/> -->
<input
type="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. 总结
-
一种组件间通信的方式,适用于任意组件间通信。
-
使用步骤:
-
在线文档: https://github.com/mroderick/PubSubJS
-
安装pubsub:
npm i pubsub-js
-
引入:
import pubsub from 'pubsub-js'
-
相关语法
import PubSub from 'pubsub-js'
// 引入PubSub.subscribe(‘msgName’, functon(msgName, data){ })
PubSub.publish(‘msgName’, data)
: 发布消息, 触发订阅的回调函数调用PubSub.unsubscribe(token)
: 取消消息的订阅
-
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 }
-
提供数据:
pubsub.publish('xxx',数据)
-
最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)
去取消订阅。 -
原生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: {
// 添加一个todo
addTodo(todoObj) {
// console.log("我是App组件,我收到了数据:", 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.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不选 -->
<!-- <input
type="checkbox"
:checked="todo.done"
@click="handleCheck(todo.id)"
/> -->
<input
type="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: {
// 添加一个todo
addTodo(todoObj) {
// console.log("我是App组件,我收到了数据:", 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;
});
},
// 修改
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不选 -->
<!-- <input
type="checkbox"
:checked="todo.done"
@click="handleCheck(todo.id)"
/> -->
<input
type="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>
<input
v-show="todo.isEdit"
type="text"
:value="todo.title"
@blur="handleBlur(todo, $event)"
/>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">
删除
</button>
<button
v-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.补充
原文地址:https://blog.csdn.net/zhzijun/article/details/143727664
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!