Vue基础15之消息订阅与发布、TodoList消息订阅与发布、TodoList编辑功能

文章介绍了在Vue项目中使用pubsub-js库进行消息订阅与发布的实现方式,包括在School.vue和Student.vue组件间的通信以及在TodoList应用中删除和编辑功能的改进。使用$nextTick确保DOM更新后执行相应操作,保证编辑功能的完善。
摘要由CSDN通过智能技术生成

消息订阅与发布

在这里插入图片描述

安装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. 一种组件间通信的方式,适用于任意组件间通信
  2. 使用步骤:
    (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">
      &nbsp;{{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">
      &nbsp;
      <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结点更新完毕之后再执行

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次DOM更新结束后执行指定的回调
  3. 什么时候用:当改变数据后,要基于更新后的新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">
      &nbsp;
      <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>

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值