Vue2 学习 (全局事件总线、消息订阅和发布、nextTick)-day 10

业务场景:

A组件写一个回调demo方法,D组件可以通过X调用demo,并且传递参数666。A组件可以收到 666

实现思路如下:

  • 配置一个中介xx 主要用来完成各种组件之间的通信,同时 x 应该具有如下特性:

    x 对于其他所有组件是可见的
  • 这个 x 能够调用 $on()、$off()、$emit() 等方法

 1- 实现依据如下图:vc身上找不到属性和方法,会去vm身上去找。

 2- vc 上面的    VueComponent.prototype_proto_ ==== Vue.prototype , 如下图绿线

 一、全局事件总线(GlobalEventBus)

全局事件总线在 Vue 中十分重要,因为利用它我们可以实现任意组件间的通信

        使用步骤,如下   1- 1 ,1-2, 1-3

最好在beforeDestroy() ,钩子函数中销毁中,调用$off()去解绑函数

 1-1 : main.js中 安装全局事件总线 $bus,如1 处代码
/*
main.js是整个项目的入口文件
*/

//引入 Vue
import Vue from 'vue'
//引入App组件,他是所有文件的父组件
import App from './App.vue'

// //构建 VueCompontent
// const Demo = Vue.extend({});
// const d = new Demo();
// // 赋值给vue(vc) 这样全局Vm都能访问到
// Vue.prototype.x = d;


Vue.config.productionTip = false
//创建Vue实例,vm
new Vue({
  render: h => h(App),
  beforeCreate(){
    //1-安装全局事件总线
    Vue.prototype.$bus = this;
  }
})
.$mount('#app')

​
 1-2:使用总线,如 2 处代码:在School组件上面绑定demo事件
<template>
  <!--组件主体-->
  <div class="school">
    <h2>学校:{{ name }}</h2>
    <h2>学校地址:{{ addr }}</h2>
  </div>
</template>

<script>
export default {
  name: "School",
  data() {
    return {
      name: "Vue学院",
      addr: "海棠大道168号",
    };
  },
  mounted() {
    // 2- this.x 在 vc身上没有要到vm身上找到
    this.$bus.$on("demo", (data) => {
      console.log("我是school组件,接收到了数据: " + data);
    });
  },
  beforeDestroy() {
    this.$bus.$off("demo");
  },
};
</script>
<style scoped>
/* 组件样式 */
.school {
  background-color: red;
  padding: 5px;
}
</style>
 1-3 :在Studnet组件,如3处代码:$emit() 调用事件
<template>
  <!--组件主体-->
  <div class="student">
    <h2>姓名:{{ name }}</h2>
    <h2>性别:{{ sex }}</h2>
    <button @click="sendStudentName()">调用全局的demo方法</button>
  </div>
</template>

<script>
export default {
  name: "Student",
  data() {
    return {
      name: "正在学习Vue学生",
      sex: "男",
    };
  },
  methods: {
    sendStudentName() {
      //3-$emit 调用 传参
      this.$bus.$emit("demo", this.name);
    },
  }
};
</script>
<style  scoped>
/* 组件样式 */
.student {
  background-color: greenyellow;
  padding: 5px;
  margin-top: 40px;
}
</style>
下面再去修改TODOLIst例子:

1-1-1安装总线路如上面 1-1 main.js

1-2-1  绑定事件,mounted() 挂载时候去绑定事件,并且在销毁前解绑

<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader @addTodo="addTodo"></MyHeader>
        <MyList :todos="todos"></MyList>

        <MyFooter
          :todos="todos"
          @checkAllBut="checkAllBut"
          @clearAll="clearAll"
        ></MyFooter>
      </div>
    </div>
  </div>
</template>

<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";

export default {
  name: "App",
  components: {
    MyHeader,
    MyList,
    MyFooter,
  },
  data() {
    return {
      todos: JSON.parse(localStorage.getItem("todos")) || [],
    };
  },
  methods: {
    //添加todo
    addTodo(x) {
      console.log("收到值:", x);
      this.todos.unshift(x);
    },
    //勾选或者取消
    checkTodo(id) {
      this.todos.forEach((obj) => {
        if (id == obj.id) {
          obj.done = !obj.done;
        }
      });
    },
    // 删除方法
    deleteTodo(id) {
      // 过滤新数组返回
      this.todos = this.todos.filter((obj) => {
        return id !== obj.id;
      });
    },
    //全选或者全不选
    checkAllBut(isAll) {
      this.todos.forEach((obj) => {
        obj.done = isAll ? true : false;
      });
    },
    // 清除所有
    clearAll() {
      this.todos = this.todos.filter((obj) => {
        return !obj.done;
      });
    },
  },
  watch: {
    todos: {
      deep: true, // 开启深度监视,监视里面的每一个对象的 done 值
      handler(value) {
        localStorage.setItem("todos", JSON.stringify(value)); // 将 value 值转化为一个JSON字符串
      },
    },
  },
  mounted() {
    // 2-在挂载时候绑定事件
    this.$bus.$on("checkTodo", (id) => {
      this.checkTodo(id);
    });
    this.$bus.$on("deleteTodo", (id) => {
      this.deleteTodo(id);
    });
  },
 // 4-销毁前解绑
   beforeDestroy() {
    this.$bus.$off("checkTodo");
    this.$bus.$off("deleteTodo");
  },
};
</script>


1-2-3  调用$emit() 方法,

<template>
  <li>
    <label>
      <input
        type="checkbox"
        :checked="todo.done"
        @change="changeCheck(todo.id)"
      />

      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" @click="handleDelte(todo.id)">删除</button>
  </li>
</template>

<script>
export default {
  name: "MyItem",
  //声明接受todo对象
  props: ["todo"],
  mounted() {
    console.log(this.todo);
  },
  methods: {
    //3-改变选中的状态
    changeCheck(id) {
      this.$bus.$emit("checkTodo", id);
    },
    //3-删除记录
    handleDelte(id) {
      if (confirm("确认删除吗?")) {
        this.$bus.$emit("deleteTodo", id);
      }
    },
  },
};
</script>

二、 消息订阅和发布

我们前面使用总局事件总线实现了所有的组件之间的通信,现在介绍另外一种技术,就是消息订阅与发布,具体流程如下:

第一步  : 订阅消息:消息名  

第二步:  发布消息:消息内容

1- 安装依赖: npm i pubsub-js 

2- 引入库  import pubsub from 'pubsub-js'

3-  订阅消息  (挂载时候)

         //4- 订阅消息 第一种
    this.pubid = pubsub.subscribe("demo", this.demo);
    //4- 订阅消息 第二种
    //箭头函数
    // this.pubid = pubsub.subscribe("demo", (msgName, data)=>{
    //   console.log("消息名称", msg);
    //   console.log("数据", data);
    // });

4-发送消息 

 // 2-发送消息
      pubsub.publish('demo', this.name)

5- 取消订阅(销毁之前)

        //3 取消订阅
     pubsub.unsubscribe(this.pubid)

School 订阅消息 ,如 1、4、3处代码

<template>
  <div class="school">
    <h2>学校:{{ name }}</h2>
    <h2>学校地址:{{ addr }}</h2>
  </div>
</template>

<script>
// 1-
import pubsub from "pubsub-js";

export default {
  name: "School",
  data() {
    return {
      name: "Vue学院",
      addr: "海棠大道168号",
    };
  },
  methods: {
    //注意是2个参数   demo(_, data) 第一个可用_占位
    demo(msgName, data) {
      console.log("消息名称", msg);
      console.log("数据", data);
    },
  },
  mounted() {
    // this.$bus.$on("demo", (data) => {
    //   console.log("我是school组件,接收到了数据: " + data);
    // });
    //4- 订阅消息 第一种
    this.pubid = pubsub.subscribe("demo", this.demo);
    //4- 订阅消息 第二种
    //箭头函数
    // this.pubid = pubsub.subscribe("demo", (msgName, data)=>{
    //   console.log("消息名称", msg);
    //   console.log("数据", data);
    // });
  },
  beforeDestroy() {
    //3 取消订阅
     pubsub.unsubscribe(this.pubid)
  },
};
</script>
<style scoped>
/* 组件样式 */
.school {
  background-color: red;
  padding: 5px;
}
</style>

 Student发送消息,如2处代码

<template>
  <!--组件主体-->
  <div class="student">
    <h2>姓名:{{ name }}</h2>
    <h2>性别:{{ sex }}</h2>
    <button @click="sendStudentName()">调用全局的demo方法</button>
  </div>
</template>

<script>
import pubsub from "pubsub-js";

export default {
  name: "Student",
  data() {
    return {
      name: "正在学习Vue学生",
      sex: "男",
    };
  },
  methods: {
    sendStudentName() {
      // this.$bus.$emit("demo", this.name);
      // 2-发送消息
      pubsub.publish('demo', this.name)
    },
  }
};
</script>
<style  scoped>
/* 组件样式 */
.student {
  background-color: greenyellow;
  padding: 5px;
  margin-top: 40px;
}
</style>

三、修改todoList的编辑逻辑(事件总线实现

1-点击编辑出现输入框

2-修改输入框文字修改并保存(失去焦点触发)

 MyItem组件

<template>
  <li>
    <label>
      <input
        type="checkbox"
        :checked="todo.done"
        @change="changeCheck(todo.id)"
      />

      <span v-show="!todo.isEdit">{{ todo.title }}</span>
      <!-- 2- 新增输入框 -->
      <input
        v-show="todo.isEdit"
        type="text"
        :value="todo.title"
        @blur="handleBlur(todo, $event)"
      />
    </label>
    <button class="btn btn-danger" @click="handleDelte(todo.id)">删除</button>
    <!-- 1-新增编辑按钮 -->
    <button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button>
  </li>
</template>

<script>
import pubsub from "pubsub-js";

export default {
  name: "MyItem",
  props: ["todo"],
  mounted() {
    console.log(this.todo);
  },
  methods: {
    changeCheck(id) {
      this.$bus.$emit("checkTodo", id);
    },
    handleDelte(id) {
      if (confirm("确认删除吗?")) {
        pubsub.publish("deleteTodo", id);
      }
    },
    // 3- 编辑操作(点击编辑显示输入框,判断是否有isEdit属性,无则 $set 绑定属性)
    handleEdit(todo) {
      if (!todo.hasOwnProperty("isEdit")) {
        //判断todo没有isEdit属性 追加属性
        this.$set(todo, "isEdit", true);
      } else {
        todo.isEdit = !todo.isEdit;
      }
    },
    //4- 失去焦点操作:保存数据
    handleBlur(todo, e) {
      todo.isEdit = !todo.isEdit;
      if(!e.target.value.trim()){
        alert('输入不能为空!')
      }
      this.$bus.$emit('editTodo',todo.id,e.target.value)
    },
  },
};
</script>

<style scoped>
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: gray;
}

li:hover button {
  display: block;
}
</style>

 APP组件中定义操作数据方法,挂载前绑定 $on 事件,销毁前并解绑 $off 方法事件

<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader @addTodo="addTodo"></MyHeader>
        <MyList :todos="todos"></MyList>

        <MyFooter
          :todos="todos"
          @checkAllBut="checkAllBut"
          @clearAll="clearAll"
        ></MyFooter>
      </div>
    </div>
  </div>
</template>

<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";

import pubsub from "pubsub-js";

export default {
  name: "App",
  components: {
    MyHeader,
    MyList,
    MyFooter,
  },
  data() {
    return {
      todos: JSON.parse(localStorage.getItem("todos")) || [],
    };
  },
  methods: {
    //添加todo
    addTodo(x) {
      console.log("收到值:", x);
      this.todos.unshift(x);
    },
    //勾选或者取消
    checkTodo(id) {
      this.todos.forEach((obj) => {
        if (id == obj.id) {
          obj.done = !obj.done;
        }
      });
    },
    // 删除方法 (_,id) _ 是为了占位
    deleteTodo(_, id) {
      console.log(id);
      // 过滤新数组返回
      this.todos = this.todos.filter((obj) => {
        return id !== obj.id;
      });
    },
    //全选或者全不选
    checkAllBut(isAll) {
      this.todos.forEach((obj) => {
        obj.done = isAll ? true : false;
      });
    },
    // 清除所有
    clearAll() {
      this.todos = this.todos.filter((obj) => {
        return !obj.done;
      });
    },
    //7- 修改
      editTodo(id, val) {
      console.log("入参", id, val);
      this.todos.forEach((obj) => {
        if (id == obj.id) {
            obj.title = val;
        }
      });
    },
  },
  watch: {
    todos: {
      deep: true, // 开启深度监视,监视里面的每一个对象的 done 值
      handler(value) {
        localStorage.setItem("todos", JSON.stringify(value)); // 将 value 值转化为一个JSON字符串
      },
    },
  },
  mounted() {
    // 3-在挂载时候绑定事件
    this.$bus.$on("checkTodo", (id) => {
      this.checkTodo(id);
    });
    // this.$bus.$on("deleteTodo", (id) => {
    //   this.deleteTodo(id);
    // });
    // 订阅删除消息
    this.pubid = pubsub.subscribe("deleteTodo", this.deleteTodo);
    // 5- 事件总线修改消息
    this.$bus.$on("editTodo", (id,value) => {
      this.editTodo(id,value);
    });
  },
  // 销毁前解绑
  beforeDestroy() {
    this.$bus.$off("checkTodo");
    this.$bus.$off("deleteTodo");
    this.$bus.$off("editTodo");
    //6- 取消订阅
    pubsub.unsubscribe(this.pubid);
  },
};
</script>

<style>
/*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-edit {
  color: #fff;
  background-color: green;
  border: 1px solid rgb(2, 98, 2);
  margin-right: 5px;
}

.btn-danger:hover {
  color: #fff;
  background-color: #bd362f;
}

.btn-edit:hover {
  color: #fff;
  background-color: green;
}

.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>

三-nextTick

1.语法: this.$nextTick(回调函数)
2.作用: 在下一次 DOM 更新结束后执行其指定的回。
3.什么时候用: 当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行

如3 处代码,修改编辑之后获取数据库焦点 

<template>
  <li>
    <label>
      <input
        type="checkbox"
        :checked="todo.done"
        @change="changeCheck(todo.id)"
      />

      <span v-show="!todo.isEdit">{{ todo.title }}</span>
      <!-- 2- 新增输入框 -->
      <input
        v-show="todo.isEdit"
        type="text"
        :value="todo.title"
        @blur="handleBlur(todo, $event)"
        ref="inputTitle"
      />
    </label>
    <button class="btn btn-danger" @click="handleDelte(todo.id)">删除</button>
    <!-- 1-新增编辑按钮 -->
    <button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button>
  </li>
</template>

<script>
import pubsub from "pubsub-js";

export default {
  name: "MyItem",
  props: ["todo"],
  mounted() {
    console.log(this.todo);
  },
  methods: {
    changeCheck(id) {
      this.$bus.$emit("checkTodo", id);
    },
    handleDelte(id) {
      if (confirm("确认删除吗?")) {
        pubsub.publish("deleteTodo", id);
      }
    },
    // 3- 编辑操作(点击编辑显示输入框,判断是否有isEdit属性,无则 $set 绑定属性)
    handleEdit(todo) {
      if (!todo.hasOwnProperty("isEdit")) {
        //判断todo没有isEdit属性 追加属性
        this.$set(todo, "isEdit", true);
      } else {
        todo.isEdit = !todo.isEdit;
      }
      //dom 节点演示渲染
      // setTimeout(() => {
      //   this.$refs.inputTitle.focus();
      // }, timeout);
        this.$nextTick(function(){
          this.$refs.inputTitle.focus();
        })
    },
    //4- 失去焦点操作:保存数据
    handleBlur(todo, e) {
      todo.isEdit = !todo.isEdit;
      if(!e.target.value.trim()){
        alert('输入不能为空!')
      }
      this.$bus.$emit('editTodo',todo.id,e.target.value)
    },
  },
};
</script>

<style scoped>
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: gray;
}

li:hover button {
  display: block;
}
</style>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值