Vue基础15
消息订阅与发布
安装pubsub-js库
npm i pubsub-js
使用
main.js
import Vue from 'vue';
import App from './App'
Vue.config.productionTip=false
new Vue({
el:"#app",
render:h=>h(App)
})
School.vue
<template>
<div class="school">
<h1>学校名称:{{name}}</h1>
<h1>学校地址:{{address}}</h1>
<h2 v-if="studentName">学生的姓名是:{{studentName}}</h2>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name: "School",
data(){
return{
name:"幸福中学",
address:"重庆市渝北区",
studentName:""
}
},
mounted(){
//消息的订阅
this.pubsubId=pubsub.subscribe("getStudentName",(msgName,data)=>{
console.log("getStudentName消息收到了,消息的名字是:"+msgName+",消息的内容是:"+data)
this.studentName=data
})
},
beforeDestroy(){
//销毁消息订阅
pubsub.unsubscribe(this.pubsubId)
}
}
</script>
<style scoped>
.school{
background-color: skyblue;
}
</style>
Student.vue
<template>
<div class="student">
<h1>学生姓名:{{name}}</h1>
<h1>学生性别:{{sex}}</h1>
<button @click="showMsg">点我输出学生姓名</button>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name: "Student",
data(){
return{
name:'李四',
sex:'女'
}
},
methods:{
showMsg(){
//消息的发布
pubsub.publish("getStudentName",this.name)
}
}
}
</script>
<style scoped>
.student{
background-color: pink;
}
</style>
总结:消息订阅与发布(pubsub)
- 一种组件间通信的方式,适用于
任意组件间通信
- 使用步骤:
(1)安装pubsub: npm i pubsub-js
(2)引入:import pubsub from ‘pubsub-js’
(3)接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
methods(){
demo(data){…}
}
…
mounted(){
this.pid=pubsub.subscribe(‘xxx’,this.demo) //订阅消息
}
(4)提供数据:pubsub.publish(‘xxx’,数据)
(5)最好在beforeDestroy钩子中,用pubsub.unsubscribe(pid)去取消订阅
TodoList-消息的订阅与发布
将Item的deleteTodo使用消息订阅与发布
App.vue
import pubsub from 'pubsub-js'
methods:{
//删除一个todo
deleteTodo(_,id){ //下划线占位
this.todos=this.todos.filter(todo=>todo.id!==id)
},
}
mounted(){
//消息的订阅实现父组件和孙子组件通信
this.pubId=pubsub.subscribe("deleteTodo",this.deleteTodo)
},
beforeDestroy() {
//销毁订阅
pubsub.unsubscribe(this.pubId)
}
MyItem.vue
import pubsub from 'pubsub-js'
methods:{
deleteItem(id){
//消息的发布
if(confirm('确认删除嘛?')) pubsub.publish("deleteTodo",id)
},
}
App.vue
<template>
<div class="bg">
<div class="todoList">
<h2 class="title">待办事项</h2>
<MyHeader @addTodo="addTodo"/>
<div class="listItem" v-show="todos.length">
<MyList :todos="todos"/>
<MyFooter :todos="todos" @clearAllTodo="clearAllTodo" @checkAllTodo="checkAllTodo"/>
</div>
</div>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
import MyHeader from "@/components/MyHeader";
import MyList from '@/components/MyList';
import MyFooter from '@/components/MyFooter'
export default {
name: "App",
components:{MyHeader, MyList, MyFooter},
data(){
return{
todos:JSON.parse(localStorage.getItem('todos'))||[]
}
},
methods:{
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
//删除一个todo
deleteTodo(_,id){ //下划线占位
this.todos=this.todos.filter(todo=>todo.id!==id)
},
//勾选or取消勾选一个todo
checkTodo(id){
this.todos.forEach((todo)=>{
if(todo.id===id){
todo.done=!todo.done
}
})
},
//清除已完成任务
clearAllTodo() {
this.todos=this.todos.filter(todo=>!todo.done)
},
//全选or取消全选
checkAllTodo(done){
this.todos.forEach(todo=>todo.done=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)
}
}
</script>
<style lang="less">
*{
padding: 0;
margin: 0;
}
.bg{
background-color: #333;
height: 937px;
padding-top: 100px;
box-sizing: border-box;
.todoList{
background-color: #fff;
width: 50%;
height: 90%;
margin: 0 auto;
//box-shadow: 5px 5px 10px 3px rgba(147, 221, 255, 0.5),-5px -5px 10px 3px rgba(147, 221, 255, 0.5); 蓝色阴影
box-shadow: 5px 5px 10px 3px rgba(0, 0, 0, 0.5),-5px -5px 10px 3px rgba(0, 0, 0, 0.5);
padding-top: 20px;
box-sizing: border-box;
.title{
text-align: center;
font-size: 30px;
font-weight: 300;
color: #00a4ff;
}
.listItem{
width: 90%;
//height: 200px;
margin: auto;
/*background-color: pink;*/
list-style: none;
border-radius: 0 0 5px 5px;
box-shadow: 1px 1px 5px 1px rgba(0,0,0,0.1),-1px -1px 5px 1px rgba(0,0,0,0.1);
padding: 20px 0;
box-sizing: border-box;
}
}
}
</style>
MyItem.vue
<template>
<div>
<li>
<!-- <input type="checkbox" name="matter" id="" v-model="todo.done">-->
<input type="checkbox" name="matter" id="" @change="checkDone(todo.id)" :checked="todo.done">
{{todo.title}}
<button class="delete" @click="deleteItem(todo.id)">删除</button>
</li>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name: "MyItem",
props:['todo'],
methods:{
deleteItem(id){
//消息的发布
if(confirm('确认删除嘛?')) pubsub.publish("deleteTodo",id)
},
checkDone(id){
//触发公共组件的某个事件
this.$bus.$emit("checkTodo",id)
}
}
}
</script>
<style scoped lang="less">
li{
//height: 35%;
//width: 96%;
display: block;
//background-color: pink;
margin: auto;
padding: 12px;
border-top: 1px solid rgba(87, 87, 87, 0.3);
//border-left: 1px solid rgba(87, 87, 87, 0.3);
//border-right: 1px solid rgba(87, 87, 87, 0.3);
//box-sizing: border-box;
border-collapse: collapse;
button{
background-color: #d9534f;
float: right;
padding: 3px 10px;
color: white;
border: 1px solid #d43f3a;
border-radius: 5px;
cursor: pointer;
&:hover{
background-color: #c9302c;
border: 1px solid #ac2925;
}
}
&:hover{
background-color: rgba(0,0,0,0.1);
}
}
</style>
MyList.vue
<template>
<div>
<ul>
<div class="con">
<MyItem v-for="todo in todos" :todo="todo" :key="todo.id"/>
</div>
</ul>
</div>
</template>
<script>
import MyItem from "@/components/MyItem";
export default {
name:'MyList',
components:{MyItem},
props:['todos']
}
</script>
<style scoped lang="less">
ul{
.con{
//width: 95%;
//margin: auto;
border-bottom: 1px solid rgba(87, 87, 87, 0.3);
border-left: 1px solid rgba(87, 87, 87, 0.3);
border-right: 1px solid rgba(87, 87, 87, 0.3);
margin: 0px 8px;
//background-color: pink;
}
}
</style>
TodoList-编辑
使用this.$set(对象名,“属性值”,设置值) 来增加对象的属性,使得Vue能够检测到数据变化,有get和set方法
App.vue
<template>
<div class="bg">
<div class="todoList">
<h2 class="title">待办事项</h2>
<MyHeader @addTodo="addTodo"/>
<div class="listItem" v-show="todos.length">
<MyList :todos="todos"/>
<MyFooter :todos="todos" @clearAllTodo="clearAllTodo" @checkAllTodo="checkAllTodo"/>
</div>
</div>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
import MyHeader from "@/components/MyHeader";
import MyList from '@/components/MyList';
import MyFooter from '@/components/MyFooter'
export default {
name: "App",
components:{MyHeader, MyList, MyFooter},
data(){
return{
todos:JSON.parse(localStorage.getItem('todos'))||[]
}
},
methods:{
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
//删除一个todo
deleteTodo(_,id){ //由于第一个参数是消息名也就是deleteTodo,这里没意义,使用下划线占位
this.todos=this.todos.filter(todo=>todo.id!==id)
},
//勾选or取消勾选一个todo
checkTodo(id){
this.todos.forEach((todo)=>{
if(todo.id===id){
todo.done=!todo.done
}
})
},
//清除已完成任务
clearAllTodo() {
this.todos=this.todos.filter(todo=>!todo.done)
},
//全选or取消全选
checkAllTodo(done){
this.todos.forEach(todo=>todo.done=done)
},
//编辑todo
editTodo(id,value){
console.log(id);
this.todos.forEach((todo)=>{
if(todo.id==id) todo.title=value
})
}
},
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)
this.$bus.$on("editTodo",this.editTodo)
},
beforeDestroy() {
//销毁公共组件的不再使用的组件
this.$bus.$off("checkTodo")
this.$bus.$off("editTodo")
//销毁订阅
pubsub.unsubscribe(this.pubId)
}
}
</script>
<style lang="less">
*{
padding: 0;
margin: 0;
}
.bg{
background-color: #333;
height: 937px;
padding-top: 100px;
box-sizing: border-box;
.todoList{
background-color: #fff;
width: 50%;
height: 90%;
margin: 0 auto;
//box-shadow: 5px 5px 10px 3px rgba(147, 221, 255, 0.5),-5px -5px 10px 3px rgba(147, 221, 255, 0.5); 蓝色阴影
box-shadow: 5px 5px 10px 3px rgba(0, 0, 0, 0.5),-5px -5px 10px 3px rgba(0, 0, 0, 0.5);
padding-top: 20px;
box-sizing: border-box;
.title{
text-align: center;
font-size: 30px;
font-weight: 300;
color: #00a4ff;
}
.listItem{
width: 90%;
//height: 200px;
margin: auto;
/*background-color: pink;*/
list-style: none;
border-radius: 0 0 5px 5px;
box-shadow: 1px 1px 5px 1px rgba(0,0,0,0.1),-1px -1px 5px 1px rgba(0,0,0,0.1);
padding: 20px 0;
box-sizing: border-box;
}
}
}
</style>
MyItem.vue
<template>
<div>
<li @dblclick="editItem(todo)">
<!-- <input type="checkbox" name="matter" id="" v-model="todo.done">-->
<input type="checkbox" name="matter" id="" @change="checkDone(todo.id)" :checked="todo.done">
<input type="text" :value="todo.title" class="editIn" v-show="todo.isEdit" @blur="ensureEdit($event,todo)" @keyup.enter="ensureEdit($event,todo)">
<span class="todoTitle" v-show="!todo.isEdit">{{todo.title}}</span>
<button class="delete" @click="deleteItem(todo.id)">删除</button>
<button class="edit" @click="editItem(todo)">编辑</button>
</li>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name: "MyItem",
props:['todo'],
methods:{
deleteItem(id){
//消息的发布
if(confirm('确认删除嘛?')) pubsub.publish("deleteTodo",id)
},
checkDone(id){
//触发公共组件的某个事件
this.$bus.$emit("checkTodo",id)
},
//编辑
editItem(todo){
if(!todo.hasOwnProperty("isEdit")){
this.$set(todo,'isEdit',true)
}else{
todo.isEdit=true
}
},
//失去焦点确认编辑成功
ensureEdit(e,todo){
todo.isEdit=false
if(!e.target.value.trim()) return alert("输入的内容不得为空")
this.$bus.$emit("editTodo",todo.id,e.target.value)
}
}
}
</script>
<style scoped lang="less">
li{
//height: 35%;
//width: 96%;
display: block;
//background-color: pink;
margin: auto;
padding: 12px;
border-top: 1px solid rgba(87, 87, 87, 0.3);
//border-left: 1px solid rgba(87, 87, 87, 0.3);
//border-right: 1px solid rgba(87, 87, 87, 0.3);
box-sizing: border-box;
//border-collapse: collapse;
.editIn{
font-size: 16px;
border: 1px solid #ccc;
padding: 5px;
border-radius: 4px;
box-sizing: border-box;
}
.editIn:focus{
border-color: #66afe9;
box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%), 0 0 8px rgb(102 175 233 / 60%);
outline: none;
}
.delete{
background-color: #d9534f;
float: right;
padding: 3px 10px;
color: white;
border: 1px solid #d43f3a;
border-radius: 5px;
cursor: pointer;
&:hover{
background-color: #c9302c;
border: 1px solid #ac2925;
}
}
.edit{
//background-color: #337ab7;
float: right;
padding: 3px 10px;
margin-right: 5px;
color: white;
border: 1px solid #4cae4c;
border-radius: 5px;
cursor: pointer;
&:hover{
border: 1px solid #398439;
//background-color: #286090;
background-color: #449d44;
border-color: #398439;
}
background-color: #5cb85c;
//border-color: #4cae4c;
}
&:hover{
background-color: rgba(0,0,0,0.1);
}
}
</style>
完善编辑功能——$nextTick
nextTick指定的回调函数会在dom结点更新完毕之后再执行
- 语法:this.$nextTick(回调函数)
- 作用:在下一次DOM更新结束后执行指定的回调
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
//编辑
editItem(todo){
if(!todo.hasOwnProperty("isEdit")){
this.$set(todo,'isEdit',true)
}else{
todo.isEdit=true
}
//由于代码是按顺序执行的,所以不会在执行完上面的代码之后就重新渲染页面
//继续向下执行,但此时的input框还是隐藏状态,隐藏状态是没办法获取焦点的,因此使用定时器来延迟获取焦点
// setTimeout(()=>{
// this.$refs.inputTitle.focus()
// })
this.$nextTick(function(){
this.$refs.inputTitle.focus()
})
},
MyItem.vue
<template>
<div>
<li @dblclick="editItem(todo)">
<!-- <input type="checkbox" name="matter" id="" v-model="todo.done">-->
<input type="checkbox" name="matter" id="" @change="checkDone(todo.id)" :checked="todo.done">
<input type="text" ref="inputTitle" :value="todo.title" class="editIn" v-show="todo.isEdit" @blur="ensureEdit($event,todo)" @keyup.enter="ensureEdit($event,todo)">
<span class="todoTitle" v-show="!todo.isEdit">{{todo.title}}</span>
<button class="delete" @click="deleteItem(todo.id)">删除</button>
<button class="edit" @click="editItem(todo)">编辑</button>
</li>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name: "MyItem",
props:['todo'],
methods:{
deleteItem(id){
//消息的发布
if(confirm('确认删除嘛?')) pubsub.publish("deleteTodo",id)
},
checkDone(id){
//触发公共组件的某个事件
this.$bus.$emit("checkTodo",id)
},
//编辑
editItem(todo){
if(!todo.hasOwnProperty("isEdit")){
this.$set(todo,'isEdit',true)
}else{
todo.isEdit=true
}
//由于代码是按顺序执行的,所以不会在执行完上面的代码之后就重新渲染页面
//继续向下执行,但此时的input框还是隐藏状态,隐藏状态是没办法获取焦点的,因此使用定时器来延迟获取焦点
// setTimeout(()=>{
// this.$refs.inputTitle.focus()
// })
this.$nextTick(function(){
this.$refs.inputTitle.focus()
})
},
//失去焦点确认编辑成功
ensureEdit(e,todo){
todo.isEdit=false
if(!e.target.value.trim()) return alert("输入的内容不得为空")
this.$bus.$emit("editTodo",todo.id,e.target.value)
}
}
}
</script>
<style scoped lang="less">
li{
//height: 35%;
//width: 96%;
display: block;
//background-color: pink;
margin: auto;
padding: 12px;
border-top: 1px solid rgba(87, 87, 87, 0.3);
//border-left: 1px solid rgba(87, 87, 87, 0.3);
//border-right: 1px solid rgba(87, 87, 87, 0.3);
box-sizing: border-box;
//border-collapse: collapse;
.editIn{
font-size: 16px;
border: 1px solid #ccc;
padding: 5px;
border-radius: 4px;
box-sizing: border-box;
}
.editIn:focus{
border-color: #66afe9;
box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%), 0 0 8px rgb(102 175 233 / 60%);
outline: none;
}
.delete{
background-color: #d9534f;
float: right;
padding: 3px 10px;
color: white;
border: 1px solid #d43f3a;
border-radius: 5px;
cursor: pointer;
&:hover{
background-color: #c9302c;
border: 1px solid #ac2925;
}
}
.edit{
//background-color: #337ab7;
float: right;
padding: 3px 10px;
margin-right: 5px;
color: white;
border: 1px solid #4cae4c;
border-radius: 5px;
cursor: pointer;
&:hover{
border: 1px solid #398439;
//background-color: #286090;
background-color: #449d44;
border-color: #398439;
}
background-color: #5cb85c;
//border-color: #4cae4c;
}
&:hover{
background-color: rgba(0,0,0,0.1);
}
}
</style>