【vue学习】处理边界情况

【vue学习】处理边界情况_第1张图片
image

访问元素 & 组件

在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的。

$root

  • 所有的子组件都可以将这个实例作为一个全局 store来访问或使用
  • 对于 demo 或非常小型的有少量组件的应用来说这是很方便的。
  • 不过这个模式扩展到中大型应用来说就不然了。
  • 因此在绝大多数情况下,我们强烈推荐使用 Vuex 来管理应用的状态。

$parent

  • $root 类似,$parent 属性可以用来从一个子组件访问父组件的实例。

  • 它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。

  • 触达父级组件会使得你的应用更难调试和理解

  • parent与children的关系








child组件被parent组件包裹

【vue学习】处理边界情况_第2张图片
image

打印结果:
【vue学习】处理边界情况_第3张图片
image

【vue学习】处理边界情况_第4张图片
image

child组件被 div包裹( div包裹几层都一样:应该原生的都不算吧?)
【vue学习】处理边界情况_第5张图片
image

【vue学习】处理边界情况_第6张图片
image

【vue学习】处理边界情况_第7张图片
image

  • prop写法
    【vue学习】处理边界情况_第8张图片
    image
  • $parent写法
    【vue学习】处理边界情况_第9张图片
    image
  • 官网栗子

在一些可能适当的时候,你需要特别地共享一些组件库。举个例子,在和 JavaScript API 进行交互而不渲染 HTML 的抽象组件内,诸如这些假设性的 Google 地图组件一样:


  
  

这个 组件可以定义一个 map 属性,所有的子组件都需要访问它。在这种情况下 可能想要通过类似 this.$parent.getMap 的方式访问那个地图,以便为其添加一组标记。

哈哈哈,然后可能有小伙伴就会用到this.$parent.$parent.map(孙子访问爷爷),然后就失控了

$refs

我们自己的项目中,这个貌似用的比较多

【vue学习】处理边界情况_第10张图片
image

$refs 只会在组件渲染完成之后生效(后面$nextTick单独一篇介绍下),并且它们不是响应式的。这仅作为一个用于直接操作子组件的【逃生舱】—— 你应该避免在模板或计算属性中访问 $refs

provide&inject

上面$parent一节讲到过this.$parent.$parent.map(孙子访问爷爷),失控了。
那如果真有这种需求呢?这就是依赖注入的用武之地,它用到了两个新的实例选项:provideinject

  • parent包裹child的情况
    【vue学习】处理边界情况_第11张图片
    image
  • div包裹child/gchild:即当前调用div的组件中定义provide选项,childgchild组件中定义inject
  • provide 选项允许我们指定我们想要提供给后代组件的数据/方法。该选项应该是一个对象或返回一个对象的函数
provide: function () {
  return {
    getMap: this.getMap
  }
}
// 或者
provide: {
    foo: 'bar'
}
  • provide/inject应用
  • 然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的属性。
    inject 选项应该是:
一个字符串数组,或
一个对象,对象的 key 是本地的绑定名,value 是:
    在可用的注入内容中搜索用的 key (字符串或 Symbol),或
    一个对象,该对象的:
        from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)
        default 属性是降级情况下使用的 value
        
inject: ['getMap']
//或
inject: {
    simpleIndex:{from:'simpleIndex', default:1000 }
}
  • Vue-cli 3.0 + Typescript 环境下
//父组件 provide
@Provide()
public componentActivity = this.getProvide()
private getProvide() {
    return 'aaaaaa'
}
// 或者
@Provide()
public componentActivity = {name:'aaaaaa'}


//后代组件 Inject
@Inject()
private componentActivity: string
private created() {
   console.info(this.componentActivity)   // 'aaaaaa'
}

程序化的事件侦听器

你通常不会用到这些,但是当你需要在一个组件实例上手动侦听事件时,它们是派得上用场的

官方案例

  1. 你可能经常看到这种集成一个第三方库的模式:
// 一次性将这个日期选择器附加到一个输入框上
// 它会被挂载到 DOM 上。
mounted: function () {
  // Pikaday 是一个第三方日期选择器的库
  this.picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })
},
// 在组件被销毁之前,
// 也销毁这个日期选择器。
beforeDestroy: function () {
  this.picker.destroy()
}
  1. 这里有两个潜在的问题:
  • 它需要在这个组件实例中保存这个 picker,如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。
  • 我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化地清理我们建立的所有东西。
  • 你应该通过一个程序化的侦听器解决这两个问题:
mounted: function () {
  var picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })

  this.$once('hook:beforeDestroy', function () {
    picker.destroy()
  })
}
  1. 使用了这个策略,我甚至可以让多个输入框元素同时使用不同的 Pikaday,每个新的实例都程序化地在后期清理它自己:
mounted: function () {
  this.attachDatepicker('startDateInput')
  this.attachDatepicker('endDateInput')
},
methods: {
  attachDatepicker: function (refName) {
    var picker = new Pikaday({
      field: this.$refs[refName],
      format: 'YYYY-MM-DD'
    })

    this.$once('hook:beforeDestroy', function () {
      picker.destroy()
    })
  }
}

$on & $off &once【vue事件总线EventBus了解下】

  1. 该章节学习自: vue篇之事件总线--:程序汪

  2. EventBus(事件总线)的简介:Vue中可以来作为事件的沟通桥梁,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件;但也就是太方便所以若使用不慎,就会造成难以维护的灾难,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。

  3. 如何使用EventBus

  • 初始化
/**单独一个js文件event-bus.js,
   定义一个变量EventBus
   局部定义
*/
/**实质上它是一个不具备 DOM 的组件,
   它具有的仅仅只是它实例方法而已,
   因此它非常的轻便
*/
import Vue from 'vue'
export const EventBus = new Vue()
/**或者直接在在main.js中初始化也可以
(这是个全局的定义,后面的具体案例先不管这个)
*/
Vue.prototype.$EventBus = new Vue()
  • 发送事件:假设你有两个子组件: DecreaseCountIncrementCount ,分别在按钮中绑定了 decrease()increment() 方法。这两个方法做的事情很简单,就是数值递减(增) 1 ,以及角度值递减(增) 180 。








 
  • 接收事件:现在我们可以在组件 App.vue 中使用 EventBus.$on(channel: string, callback(payload1,…))监听 DecreaseCountIncrementCount 分别发送出了 decreasedincremented 频道。




  • 用一张图来描述示例中用到的 EventBus 之间的关系:


    【vue学习】处理边界情况_第12张图片
    image
  • 如果你只想监听一次事件的发生,可以使用 EventBus.$once(channel: string, callback(payload1,…))

  • 移除事件监听者:

import { eventBus } from './event-bus.js'
EventBus.$off('decreased', {})
  • 你也可以使用 EventBus.$off('decreased') 来移除应用内所有对此事件的监听。或者直接调用EventBus.$off() 来移除所有事件频道, 注意不需要添加任何参数 。
  • 上面就是 EventBus 的使用方式,是不是很简单。PS:每次使用 EventBus 时都需要在各组件中引入 event-bus.js
  • 事实上,我们还可以通过别的方式,让事情变得简单一些。那就是创建一个全局的 EventBus
  1. 全局的 EventBus

全局EventBus,虽然在某些示例中不提倡使用,但它是一种非常漂亮且简单的方法,可以跨组件之间共享数据。它的工作原理是发布/订阅方法,通常称为 Pub/Sub

【vue学习】处理边界情况_第13张图片
image
我们从上图中可以得出以下几点:
a.有一个全局EventBus
b.所有事件都订阅它
c.所有组件也发布到它,订阅组件获得更新

总结一下:
a.所有组件都能够将事件发布到总线,
b.然后总线由另一个组件订阅,
c.然后订阅它的组件将得到更新
var EventBus = new Vue();
Object.defineProperties(Vue.prototype, {
    $bus: {
        get: function () {
            return EventBus
        }
    }
})
/**
现在,这个特定的总线使用两个方法 $on 和 $emit 一个用于创建发出的事件,它就是$emit 
另一个用于订阅 $on 
*/
this.$bus.$emit('nameOfEvent',{ ... pass some event data ...});

this.$bus.$on('nameOfEvent',($event) => {
    // ...
})

现在我们创建两个简单的组件:一个 ShowMessage 的组件用来显示信息,另外创建一个 UpdateMessage 的组件,用来更新信息。








  1. EventBus注册在全局上时,路由切换时会重复触发事件
  • 看了篇文章:vue中eventbus被多次触发

  • 全局定义的事件是不会跟随组件的生命周期函数进行状态改变的。

  • 切换路由时,如果不手动清除事件的话,它会注册多次。

  • 手动清除事件

created() {
    this.bus.$off('clickBus');
    //在每次创建事件之前,手动清除定义的事件
    //根据实际的业务需求也可以在beforeDestroy()和destroyed()
},
mounted(){
    this.bus.$on('clickBus', (e) => {})
}

$once【定时器销毁案例看下】

  1. 这个首先看下官方案例,即【程序化的事件侦听器】刚开始讲到的那个
  2. 方案1:


    【vue学习】处理边界情况_第14张图片
    image

    该方案有两点不好的地方,引用尤大的话来说就是:

  • (1)它需要在这个组件实例中保存这个数据timer,这是多余的。
  • (2)我们的建立定时器代码独立于我们的清理代码(定时器需要卸载页面的时候卸载),这使得我们比较难于程序化的清理我们建立的所有东西(意思是执行代码和清除代码不在一起)。
  1. 方案2(同官网案例)


    【vue学习】处理边界情况_第15张图片
    image

循环引用

递归组件(自己调自己)

组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事(动态组件和异步组件一章中我们提起过name的作用):

name: 'unique-name-of-my-component'

当你使用 Vue.component 全局注册一个组件时,这个全局的 ID 会自动设置为该组件的 name 选项。

Vue.component('unique-name-of-my-component', {
  // ...
})

稍有不慎,递归组件就可能导致无限循环:

name: 'stack-overflow',
template: '
'

A组件与B组件相互调用

这个我们也在动态组件和异步组件的“异步组件”中讲到了
组件之间的循环引用

模板定义的替代品

A. 内联模板inline-template

Vue 学习笔记 — inline-template

定义一个私有子组件时,如果子组件的template过长会使得代码非常难以阅读


【vue学习】处理边界情况_第16张图片
image

这时可以使用内联模版


【vue学习】处理边界情况_第17张图片
image

不过,inline-template 会让模板的作用域变得更加难以理解。所以作为最佳实践,请在组件内优先选择 template 选项或 .vue 文件里的一个