/**PageBeginHtml Block Begin **/ /***自定义返回顶部小火箭***/ /*生成博客目录的JS 开始*/ /*生成博客目录的JS 结束*/

Vue中的 消息订阅与发布(pubsub)及TodoList 示例




Vue中的消息订阅

1:内容说明:


消息订阅与发布(pubsub)
一种组件间通信的方式,适用于任意组件间通信。

使用步骤:

安装pubsub:npm i pubsub-js

引入: import pubsub from 'pubsub-js'

接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

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

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


image

image

需要数据的组件 订阅消息  ---->第三方js库:如 pubsub-js(publish subscribe js)

提供数据的组件 发布消息




2:增加 第三方js库:Pubsub-js


PS E:\Vue_demo\VueCli\vue_test> npm i pubsub-js

added 1 package in 17s
PS E:\Vue_demo\VueCli\vue_test>

image

image






3:界面示例:


image

4: 全局事件总线 与消息订阅的使用说明


1:在实际开发中,全局事件总线的使用频率要高于 消息订阅的实现方式。因为 全局事件总线的实现功能与消息订阅方式基本相同。且全局事件总线是由 vue组件提供的。

2:如下图所示:在功能中此时 X 是 消息订阅,非全局事件总线

image


5:代码示例


5-1:代码示例结构:

image


5-2:main.js

 //引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue生产提示
Vue.config.productionTip=false;
/*
// Vue的线程总线的配置数据存储中间对象。
const  vcObj=Vue.extend({});
const  vc=new vcObj();
Vue.prototype.x=vc;
*/

// 创建Vm
const vm = new Vue(  {
        el:'#app',
        render: (h) => h(App),
      //   演示消息订阅时,取消全局事件总线
      //   beforeCreate () {
      //      // Vue的线程总线的配置数据存储中间对象    
      //      Vue.prototype.$bus=this;  //安装全局事件总线
      //   }
   });


5-3:App.vue

<template>
  <div>
    <hr/>
    <div class="divCss">
        <h1 class="h1Css">{{msg}}</h1>
        <hr/><br/>
        <School />
        <hr/><br/>
        <Student />
        <hr/><br/>
    </div>

  </div>
</template>
<script>

    import School from './components/School.vue';
    import Student from './components/Student.vue';


    export default {
        name:'App',
        data(){
          return{
            msg:"Vue_线程总线",
            studentTomName:''
          }
        },
        components:{
            Student,
            School
        }
    }
</script>
<!--
  1指定Css 样式的编写方式
  less 最大特点内容可以嵌套着写

 -->
<style lang="less">
   .divCss{
        background-color: chocolate;
        margin: auto;
        padding: 20px;

        .h1Css{
        font-size: 36px;
        color: white;
   }
   }

</style>



5-4:School.vue


<!--
  ## scoped样式

  1. 作用:让样式在局部生效,防止冲突。
  2. 写法:```<style scoped>```
 -->
<template>
    <div  class="ClassStyle">
      <h1  style="color:red">{{msg}}</h1>
      <h2>学校名称:{{name }}</h2>
      <h2>学校地址:{{address}}</h2>
    </div>
</template>
  <script>

      // 引入 pubsub-js
      import pubsub from 'pubsub-js';


      export default {
        name:'School',
        data () {
          return {
            msg:'学校信息',
            name:'华南师范大学',
            address:'广州市天河区中山大道西55号'
          }
        },
        mounted () {
          console.log("School",this.$bus);
          // 演示消息订阅,因注释掉里全局事件总线,故此处全局事件总线的调用也需要注释掉
          // this.$bus.$on('hello',(data)=>{
          //   console.log('我是School组件, 收到了数据:',this.name);
          // });

          //消息订阅:

         /*
         //方式一:
         this.pubSubId= pubsub.subscribe('hello',function(msgName,msgData){
              // 说明:这里的this 第三方组件。非Vue组件的this
              console.log(this);
              console.log('有人发布里 hello 消息,hello 消息的回调执行了',msgName,msgData);
          });
          */
          // 方式二: 
          /*
          this.pubSubId= pubsub.subscribe('hello',(msgName,msgData) =>{
              // 说明:这里的this 为Vue组件的this
              console.log(this);
              console.log('有人发布里 hello 消息,hello 消息的回调执行了',msgName,msgData);
          });
          */
          // 方式三:将接收的回调函数直接传递到 methods里的某个方法中:
          this.pubSubId1= pubsub.subscribe('hello',this.pubSubMethods);

        },
        methods:{
          pubSubMethods(msgName,msgData){
            console.log('有人发布里 hello 消息,hello 消息的回调执行了',msgName,msgData);
          }
        },
        beforeDestroy () {
          // 演示消息订阅,因注释掉里全局事件总线,故此处全局事件总线的调用也需要注释掉
          //  this.$bus.$off('hello');

          //消息订阅:取消订阅消息
          pubsub.unsubscribe(this.pubSubId);
        }

      }
  </script>
 <!--scoped 控制组件的Css样式为局部样式不会为同名的cs样式名的冲突导致样式覆盖  -->
  <style  scoped>
    .ClassStyle{
      background-color: aquamarine;
      padding: 5px;
    }
  </style>



5-5:Student.vue


<!--
 ## scoped样式

1. 作用:让样式在局部生效,防止冲突。
2. 写法:```<style scoped>```
 -->
<template>
    <div  class="ClassStyle">
      <h1  style="color:red">{{msg}}</h1>
      <h2>学生名称:{{name}}</h2>
      <h2>学生性别:{{sex}}</h2>
      <button @click="sendStudentNameToSchool">把学生名给School组件</button>
    </div>
</template>

  <script>
   // 引入 pubsub-js
   import pubsub from 'pubsub-js';

      export default {
        name:'Student',
        data () {
          return {
            msg:'学生信息:One',
            name:'向北',
            sex:'男',
            score:12
          }
        },
        mounted () {
          console.log("Student",this.$bus);
        },
        methods:{
          sendStudentNameToSchool(){
            // 演示消息订阅,因注释掉里全局事件总线,故此处全局事件总线的调用也需要注释掉
            // this.$bus.$emit('hello',666);
            pubsub.publish('hello',666);
          }
        }

      }
  </script>
 <!--scoped 控制组件的Css样式为局部样式不会为同名的cs样式名的冲突导致样式覆盖  -->
 <style  scoped>
    .ClassStyle{
      background-color:lightskyblue;
      padding: 5px;
    }
  </style>












TodoList 示例中的消息订阅



1:示例说明:搞清楚消息发布方与消息订阅方



image


2:代码示例


2-1:界面效果

image


2-2:ToItem.vue


<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done"  @change="handleCheck(todo.id)" />
            <!-- 下面的方案可以 替代 @change="handleCheck(todo.id)" 作功能实现,不推荐。因为违反原则,修改里props里的对象数据,vue没有监测到 -->
            <!-- <input type="checkbox"  v-model="todo.done" /> --><!-- 一般不建议这么使用  v-model="todo.done"  调用 props传递过来的对象里的数据信息进行数据操作改变-->
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="delTodosInfo(todo.id)">删除</button><!-- style="display:none"  控制按钮隐藏 -->
    </li>
</template>
<script>
/**
 * 引入库管理说明:
 *  一般自己写的组件都放在下面
 *  引入第三方的组件 都放在上面
 */
// 导入消息订阅组件:
import pubsub from 'pubsub-js';



export default {
    name: 'ToItem',
    // 说明接收todo对象
    props:['todo'],  /*  作事件总线示例:去掉父传递子,子传递孙的 示例,改为事件总线操作:  ,'onChangeTodosOfDoneInfo','deleteTodosById' */
    mixins: [],
    components: {},
    data() {
        return {
        }
    },
    computed: {},
    watch: {},
    created() { },
    mounted() { },
    methods: {
      //勾选 或者 取消勾选
        handleCheck(id){
            //通知
            // this.onChangeTodosOfDoneInfo(id);
            this.$bus.$emit('onChangeTodosOfDoneInfo',id);
        },
        // 删除
        delTodosInfo(id){
            if(confirm('确定删除吗?')){
              console.log(id);
              // this.deleteTodosById(id);

              //  演示消息订阅: 注释掉全局事件总线
              // this.$bus.$emit('deleteTodosById',id);
              console.log("开始消息传递"+id );
              pubsub.publish('deleteTodosById',id);

            }
        }

    }
}
</script>
<style scoped lang="less">
/*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-3:ToList.vue


<template>
    <ul class="todo-main">
        <ToItem
            v-for="todoObj in todos"
            :key="todoObj.id"
            :todo="todoObj"
        />  <!--
                作事件总线示例:去掉父传递子,子传递孙的 示例,改为事件总线操作:
                :onChangeTodosOfDoneInfo="onChangeTodosOfDoneInfo"
                :deleteTodosById="deleteTodosById"
            -->
    </ul>
</template>
<script>
import ToItem from './ToItem.vue';


export default {
    name: 'ToList',
    mixins: [],
    components: {
        ToItem: ToItem
    },
    props: ['todos'], /*  作事件总线示例:去掉父传递子,子传递孙的 示例,改为事件总线操作:, 'onChangeTodosOfDoneInfo','deleteTodosById' */
    data() {
        return {

        }
    },
    computed: {},
    watch: {},
    created() { },
    mounted() { },
    methods: {}
}
</script>
<style scoped lang="less">
/*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>


2-4:ToListFooter.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: 'ToListFooter',

    mixins: [],
    components: {},
    props: ['todos','checkAllTodos','ckearAllTodo'],
    data() {
        return {
        }
    },
    computed: {
      // 统计总数
      total(){
        return this.todos.length;
      },
      // 统计勾选数量
      doneTotal(){
        // 计数统计实现方式一:
          let total=0;
          this.todos.forEach((todo)=>{
                if(todo.done){
                  total++;
                }
          } );
        // return total; 
        //计数统计实现方式二: 
         const td= this.todos.reduce((index,todo)=>{
            return index+(todo.done?1:0);
         },0);
         console.log("返回已完成结果值:",td);
        // 返回结果值
        //  return td;

         //计数统计实现方式三:
        return this.todos.reduce((index,todo)=>index+(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(checked){
          return this.checkAllTodos(checked);
       }
      }
    },
    watch: {},
    created() { },
    mounted() { },
    methods: {
      // 全选,或者 取消全选
      checkAll(event){
          this.checkAllTodos(event.target.checked);
      },
      // 清除已完成事项
      clearAll(){
        this.ckearAllTodo();
      }
     }

}
</script>
<style scoped lang="less">
/*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>




2-5:ToListTop.vue


<template>
    <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title"  @keyup.enter="addTodoInfo"/>
    </div>
</template>
<script>

import {nanoid} from 'nanoid'

export default {
    name: 'ToListTop',
    mixins: [],
    components: {},
    props: ['addTodoInfos'],/* addTodoInfos:接收到父组件的传递过来的方法 ,不同组件的相互调用的方法不能重名*/
    data() {
        return {
            title:""
        }
    },
    computed: {},
    watch: {},
    created() { },
    mounted() { },
    methods: {
        addTodoInfo(event){
            // 校验数据
            if(this.title.trim()=="" /* event.target.value */){
                return  alert("输入内容不能为空");
            }
            const todo={id:nanoid(),title:this.title/* event.target.value */,done:false};
            console.log(todo);
            this.addTodoInfos(todo);/* 将子组件的数据通过调用父组件的方式传递给父组件里的todos对象里 */
            /* event.target.value=""; */
            this.title=""; //清空数据
        }
    }
}
</script>
<style scoped lang="less">
/*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>



2-6:App.vue


<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <ToListTop :addTodoInfos="addTodoInfos" /><!-- :addTodoInfos="addTodoInfos" 跨组件传递方法并符合数据对象 -->
        <ToList :todos="todos"
        />  <!-- :todos="todos" 跨组件进行传值;:onChangeTodosOfDoneInfo="onChangeTodosOfDoneInfo":组件间数据方法的逐层传递 -->
        <!--    作事件总线示例:去掉父传递子,子传递孙的 示例,改为事件总线操作
                :onChangeTodosOfDoneInfo="onChangeTodosOfDoneInfo"
                :deleteTodosById="deleteTodosById"  -->
        <ToListFooter  :todos="todos" :checkAllTodos="checkAllTodos" :ckearAllTodo="ckearAllTodo" />
      </div>
    </div>
  </div>
</template>
<script>
/**
 * 引入库管理说明:
 *  一般自己写的组件都放在下面
 *  引入第三方的组件 都放在上面
 */
// 导入消息订阅组件:
import pubsub from 'pubsub-js';


import ToListTop from './components/ToListTop.vue';
import ToList from './components/ToList.vue';
import ToListFooter from './components/ToListFooter.vue';


export default {
  name: 'App',
  components: {
    ToListTop: ToListTop,
    ToList: ToList,
    ToListFooter: ToListFooter
  },
  data() {
    return {
      todos: JSON.parse(localStorage.getItem('todos')) ||[]
    }
  },
    computed: {},
    watch: {
      todos:{
         deep:true,//深度监控
         handler(value){
            localStorage.setItem('todos',JSON.stringify(value));
         }
      }
    },
    created() { },
    mounted() {

       this.$bus.$on('onChangeTodosOfDoneInfo',this.onChangeTodosOfDoneInfo);
      //  演示消息订阅: 注释掉全局事件总线
      //  this.$bus.$on('deleteTodosById',this.deleteTodosById);
      this.pubSubId=pubsub.subscribe('deleteTodosById',this.deleteTodosById);

    },
    beforeDestroy(){
       this.$bus.$off('onChangeTodosOfDoneInfo');
       //  演示消息订阅: 注释掉全局事件总线
       //  this.$bus.$off('deleteTodosById');
       pubsub.unsubscribe(this.pubSubId);

    },
    methods: {
      // 添加1个todaoObj
      addTodoInfos(todoObj){
         console.log("App组件,接收到数据",todoObj);
         this.todos.unshift(todoObj);
      },
      // 勾选或者取消勾选1个todoObj
      onChangeTodosOfDoneInfo(id){
          this.todos.forEach((todo)=>{
             if(todo.id==id){
               todo.done =!todo.done; //赋值取反操作
             }
          });
      },
      //删除一个todo
      /*
       *  _, :表示参数占位
       * 注意:通过 pubsub-js 第三方插件实现消息订阅,消息接收方的回调参数 是由2个的 第一个为消息名称 msgName ,第二个参数为 消息数据msgData
       */
      deleteTodosById(_,id){
        this.todos=this.todos.filter((todo) =>{
          console.log("接收到 消息订阅的内容"+id);
           return todo.id !==id;
        });
      },
      // 全选  或者  取消全选
      checkAllTodos(done){
        this.todos.forEach((todo)=>{
            todo.done=done;
        });
      },
      // 清除所有已经完成的todo事项
      ckearAllTodo(){
        this.todos=this.todos.filter((todo)=>{
          return !todo.done;
        });
      }
    }

}
</script>
<!--
  1指定Css 样式的编写方式
  less 最大特点内容可以嵌套着写

 -->
<style lang="less">
.divCss {
  background-color: chocolate;
  margin: auto;
  padding: 20px;

  .h1Css {
    font-size: 36px;
    color: white;
  }
}

/*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>



2-7:main.js




//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue生产提示
Vue.config.productionTip=false;

// 创建Vm
const vm = new Vue(  {
        el:'#app',
        render: (h) => h(App),
        //添加全局事件总线对象
        beforeCreate(){
             Vue.prototype.$bus=this;
        }
   });






















posted @ 2023-01-21 17:38  一品堂.技术学习笔记  阅读(167)  评论(0编辑  收藏  举报