Vue官方文档学习-处理边界情况

1.背景

一些需要对 Vue 的规则做一些小调整的特殊情况,这些功能都是有劣势或危险的场景的

2.访问元素&组件

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

(1) 访问根实例

  • 在每个 new Vue 实例的子组件中,其根实例可以通过 $root 属性进行访问;
  • 所有的子组件都可以将这个实例作为一个全局 store 来访问或使用;
  • 【注意】对于 demo 或非常小型的有少量组件的应用来说这是很方便的。不过这个模式扩展到中大型应用来说就不然了。因此在绝大多数情况下,推荐使用 Vuex来管理应用的状态;
// Vue 根实例
new Vue({
  data: {
    foo: 1
  },
  computed: {
    bar: function () { /* ... */ }
  },
  methods: {
    baz: function () { /* ... */ }
  }
})
//==== 子组件访问根组件 ====
// 获取根组件的数据
this.$root.foo

// 写入根组件的数据
this.$root.foo = 2

// 访问根组件的计算属性
this.$root.bar

// 调用根组件的方法
this.$root.baz()

(2) 访问父级组件实例

  • $root 类似,$parent 属性 可以用来从一个子组件访问父组件的实例;
  • $parent提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 属性 的方式传入子组件的方式。
  • 【注意】访问父组件会使得应用更难调试和理解,尤其是当变更了父级组件的数据的时候。当稍后回看那个组件的时候,很难找出那个变更是从哪里发起的;
  • 共享组件库时,如在和 JavaScript API 进行交互而不渲染 HTML 的抽象组件内,<google-map>组件可以定义一个 map property,所有的子组件都需要访问它;
<google-map>
  <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
</google-map>
  • 在这种情况下<google-map-markers> 可能想要通过类似this.$parent.getMap 的方式访问那个地图,以便为其添加一组标记。通过这种模式构建出来的那个组件的内部仍然是容易出现问题的。比如,设想一下添加一个新的 <google-map-region> 组件,当 <google-map-markers>在其内部出现的时候,只会渲染那个区域内的标记:
<google-map>
  <google-map-region v-bind:shape="cityBoundaries">
    <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
  </google-map-region>
</google-map>
  • 那么在<google-map-markers>内部可能发现自己需要一些类似这样的 hack:
var map = this.$parent.map || this.$parent.$parent.map
  • 很快它就会失控(组件嵌套加深时),所以当需要向更深层级的组件提供上下文信息时推荐依赖注入

(3) 访问子组件实例或子元素

  • 需要在javascript里直接访问一个子组件,可以通过ref这个属性为子组件赋予一个ID引用
<!-- 定义ref -->
<base-input ref="usernameInput"></base-input>
// 使用ref访问<base-input>实例
this.$refs.usernameInput
  • <base-input>中,可以使用一个类似的ref提供对内部这个指定元素的访问,并在父组件定义focus()方法:
<!-- base-input组件 -->
<input ref="input">
// 父组件中定义focus方法
methods: {
  // 用来从父级组件聚焦输入框
  focus: function () {
    this.$refs.input.focus()
  }
}
// 在父组件聚焦<base-input>里的输入框:
this.$refs.usernameInput.focus()
  • 【注意1】当refv-for 一起使用的时候,得到的 ref将会是一个包含了对应数据源的这些子组件的数组
  • 【注意2】$refs只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”,应该避免在模板或计算属性中访问 $refs

(4).依赖注入


1) 场景

  • 组件嵌套较深,使用$parent无法很好的访问父组件数据和方法;
  • 如下所示,所有<google-map>的后代都需要访问一个getMap方法,以便知道要跟那个地图进行交互,但是使用$parentproperty无法很好的扩展到更深层及的嵌套组件上;
<google-map>
  <google-map-region v-bind:shape="cityBoundaries">
    <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
  </google-map-region>
</google-map>

2) provide

  • provide选项允许指定想要提供给后代组件的数据/方法;
  • 在上述案例中,<google-map>内部的getMap方法即为想提供给后代的方法
provide: function () {
  return {
    getMap: this.getMap
  }
}

3) inject

  • 使用inject选项来接收指定的想要添加在这个实例上的propert:
// 在子组件中添加
inject:['getMap']

4) 总结

  • 相比$parent来说,provideinject可以实现在任意后代组件中访问getMap,而不需要暴露整个<google-map>实例

3.程序化的事件侦听器


(1) 场景

当需要在一个组件实例上手动侦听事件时

(2)事件侦听器

  • $on(eventName,eventHandler)侦听一个事件;
  • $once(eventName,eventHandler)一次性侦听一个事件;
  • $off(eventName,eventHanlder)停止侦听一个事件;
  • Vue 的事件系统不同于浏览器的 EventTarget API。尽管它们工作起来是相似的,但是 $emit$on, 和 $off并不是 dispatchEventaddEventListenerremoveEventListener 的别名;
  • 用于代码组织工具,举例:在某个第三方库模式下:
// 一次性将这个日期选择器附加到一个输入框上
// 它会被挂载到 DOM 上。
mounted: function () {
  // Pikaday 是一个第三方日期选择器的库
  this.picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })
},
// 在组件被销毁之前,也销毁这个日期选择器。
beforeDestroy: function () {
  this.picker.destroy()
}
  • 上述代码存在两个问题:
    1) 它需要在这个组件实例中保存这个 picker,如果可以的话最好只有生命周期钩子可以访问到它;
    2) 建立代码独立于清理代码,这使得难于程序化地清理建立的所有东西;
  • 通过程序化的侦听器解决:
mounted: function () {
  var picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })

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

// 让多个输入框元素同时使用不同的 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()
    })
  }
}

4.循环引用

  • 组件是可以在它们自己的模板中调用自身的。只能通过 name 选项来做这件事;
name: 'unique-name-of-my-component'
  • 当使用 Vue.component 全局注册一个组件时,这个全局的 ID 会自动设置为该组件的 name 选项:
Vue.component('unique-name-of-my-component', {
  // ...
})
  • 组件可能出现无限循环,导致“max stack size exceeded”错误,所以一定要确保递归调用是条件性的(如使用一个最终会得到falsev-if
name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'

(1) 组件之间的循环引用

1) 场景

构建一个文件目录树,像资源管理器一样,如<tree-folder>组件,内部渲染<tree-folder-contents>组件,但是<tree-folder-contents>组件内部又依赖于<tree-folder>组件,如此产生循环引用;

<!-- tree-folder组件 -->
<p>
  <span>{{ folder.name }}</span>
  <tree-folder-contents :children="folder.children"/>
</p>

<!-- tree-folder-contents 组件-->
<ul>
  <li v-for="child in children">
    <tree-folder v-if="child.children" :folder="child"/>
    <span v-else>{{ child.name }}</span>
  </li>
</ul>

2) 解决方案

  • 需要给模块系统一个点,在那里<tree-folder>是需要<tree-folder-contents>的,但是我们不需要先解析<tree-folder-contents>
  • 在例子中,<tree-folder>组件被设为了那个点,产生悖论的子组件是<tree-folder-contents>,所以我们会等到生命周期钩子beforeCreate时去注册它:
beforeCreate: function () {
  this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}
  • 或者在本地注册组件的时候,使用webpack的异步import
components: {
  TreeFolderContents: () => import('./tree-folder-contents.vue')
}

5.模板定义的替代品

(1) 内联模板

  • inline-template这个特殊的 attribute 出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容;
<my-component inline-template>
  <div>
    <p>These are compiled as the component's own template.</p>
    <p>Not parent's transclusion content.</p>
  </div>
</my-component>
  • 内联模板需要定义在 Vue 所属的 DOM 元素内;
  • inline-template 会让模板的作用域变得更加难以理解。所以作为最佳实践,应在组件内优先选择template选项或 .vue 文件里的一个 <template>元素来定义模板;

(2) X-Template

  • 另一个定义模板的方式是在一个 <script> 元素中,并为其带上 text/x-template 的类型,然后通过一个 id 将模板引用过去。例如:
<!-- 定义 -->
<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
// 使用
Vue.component('hello-world', {
  template: '#hello-world-template'
})
  • x-template需要定义在 Vue 所属的 DOM 元素;

6.控制更新

(1) 强制更新

  • 通过$forceUpdate实现

(2) 通过v-once创建低开销的静态组件

1) 场景

当某个组件包含大量静态内容,希望这些内容只计算一次然后缓存起来

2) 使用

  • 在根元素上添加v-once可以确保这些内容只计算一次然后缓存起来
Vue.component('terms-of-service', {
  template: `
    <div v-once>
      <h1>Terms of Service</h1>
      ... a lot of static content ...
    </div>
  `
})
  • 【注意】除非在页面需要的大量渲染静态内容并且发现渲染变慢的情况下,否则应该尽量避免使用v-once,否则可能会因为开发人员漏看v-once导致无法发现模板未正确更新的原因。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值