1️⃣ 组件边界情况

这里记录的都是和处理边界情况有关的功能,即一些需要对 Vue 的规则做一些小调整的特殊情况。不过注意这些功能都是有劣势或危险的场景的。我们会在每个案例中注明,所以当你使用每个功能的时候请稍加留意。

1️⃣ 访问元素 & 组件

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

2️⃣ $root - 访问根实例

在每个 new Vue 实例的子组件中,子组件可以通过 $root property 进行访问根实例的数据。

注意:对于 demo 或非常小型的有少量组件的应用来说这是很方便的。不过这个模式扩展到中大型应用来说就不然了。因此在绝大多数情况下,我们强烈推荐使用 Vuex 来管理应用的状态。

  1. // 在任何一个子组件使用都会访问到跟组件的数据
  2. this.$root.msg

image.png

2️⃣ $parent - 访问父级组件实例

和 $root 类似,$parent property 可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。

注意:在绝大多数情况下,触达父级组件会使得你的应用更难调试和理解,尤其是当你变更了父级组件的数据的时候。当我们稍后回看那个组件的时候,很难找出那个变更是从哪里发起的。

image.png

2️⃣ provide 和 inject 依赖注入

在上面的例子中,利用 $parent 属性,没有办法很好的扩展到更深层级的嵌套组件上。这也是依赖注入的用武之地,它用到了两个新的实例选项:provide 和 inject。

  1. 1. provide 选项允许我们指定想要提供给后代组件的数据/方法
  2. 2. 然后再任何后代组件中,我们都可以使用 inject 选项来接收指定想要添加在实例上的属性

实际上,你可以把依赖注入看作一部分“大范围有效的 prop”,除了:

  1. 1. 祖先组件不需要知道哪些后代组件使用它提供的 property
  2. 2. 后代组件不需要知道被注入的 property 来自哪里

image.png

然而,依赖注入还是有负面影响的。它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。同时所提供的 property 是非响应式的。这是出于设计的考虑,因为使用它们来创建一个中心化规模化的数据跟使用 $root 做这件事都是不够好的。如果你想要共享的这个 property 是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像 Vuex 这样真正的状态管理方案了。

2️⃣ ref - 访问子组件实例或子元素

尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref 这个 attribute 为子组件赋予一个 ID 引用。例如:

  1. 1. ref:将子组件的实例或子元素添加到父组件的 $refs
  2. 2. $ref:包含了所有子组件或子元素的 ref 列表

image.png
refv-for 一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。

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

1️⃣ 程序化的事件侦听器

现在,你已经知道了 $emit 的用法,它可以被 v-on 侦听,但是 Vue 实例同时在其事件接口中提供了其它的方法。我们可以:

  1. 1. 通过 $on(eventName, eventHandler) 侦听一个事件
  2. 2. 通过 $once(eventName, eventHandler) 只监听一次
  3. 3. 通过 $off(eventName, eventHandler) 停止侦听一个事件

注意 Vue 的事件系统不同于浏览器的 EventTarget API。尽管它们工作起来是相似的,但是 $emit、$on, 和 $off 并不是 dispatchEvent、addEventListener 和 removeEventListener 的别名。

2️⃣ $on

image.png
56.gif

1️⃣ 循环引用

2️⃣ 递归组件

递归组件很容造成栈溢出, 所有使用递归组件时, 要注意有出口.

组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事:

  1. export default {
  2. // 组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事:
  3. // 当要递归组件时要给组件一个名字 否则将要报错
  4. name: "ComponentOne",
  5. };

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

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

使用示例

  1. <!-- >>>>>>>>>> App.vue >>>>>>>>>> -->
  2. <template>
  3. <div id="app">
  4. <component-one :list="list"></component-one>
  5. </div>
  6. </template>
  7. <script>
  8. import ComponentOne from "./components/ComponentOne.vue";
  9. export default {
  10. components: { ComponentOne },
  11. data() {
  12. return {
  13. list: [
  14. { name: "A" },
  15. {
  16. name: "B",
  17. arr: [
  18. { name: "B1-1" },
  19. {
  20. name: "B1-2",
  21. arr: [{ name: "B2-1" }, { name: "B2-2" }, { name: "B2-3" }],
  22. },
  23. { name: "B1-3" },
  24. ],
  25. },
  26. { name: "C" },
  27. ],
  28. };
  29. },
  30. };
  31. </script>
  1. <!-- >>>>>>>>>> ComponentOne.vue >>>>>>>>>> -->
  2. <template>
  3. <ul class="co">
  4. <li v-for="(item, index) in list" :key="index">
  5. {{ item.name }}
  6. // 调用自己
  7. <componentOne :list="item.arr"></componentOne>
  8. </li>
  9. </ul>
  10. </template>
  11. <script>
  12. export default {
  13. // 组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事:
  14. // 当要递归组件时要给组件一个名字 否则将要报错
  15. name: "ComponentOne",
  16. props: {
  17. list: {
  18. type: Array,
  19. default: () => [],
  20. },
  21. },
  22. };
  23. </script>

image.png

2️⃣ 组件之间的循环引用

有时,在去构建一些组件时,会出现组件互为对方的后代/祖先:

  1. Vue.component("cmp-a", {
  2. template: `
  3. <div>
  4. <cmp-b></cmp-b>
  5. </div>
  6. `,
  7. });
  8. Vue.component("cmp-b", {
  9. template: `
  10. <div>
  11. <cmp-a></cmp-a>
  12. </div>
  13. `,
  14. });

此时,我们使用的是全局注册组件,并不会出现悖论,但是如果使用的为局部组件就会出现悖论。
但是即使用了全局注册组件,在使用 webpack 去导入组件时,也会出现一个错误:Failed to mount component: template or render function not defined。
模块系统发现它需要 A,但是首先 A 依赖 B,但是 B 又依赖 A,但是 A 又依赖 B,如此往复。这变成了一个循环,不知道如何不经过其中一个组件而完全解析出另一个组件。为了解决这个问题,我们需要给模块系统一个点:“A 反正是需要 B 的,但是我们不需要先解析 B。”

  1. beforeCreate () {
  2. this.$options.components.CmpB = require('./tree-folder-contents.vue').default;
  3. }

或者,在本地注册组件的时候,你可以使用 webpack 的异步 import:

  1. components: {
  2. CmpB: () => import("./tree-folder-contents.vue");
  3. }

1️⃣ 模板定义的替代品

2️⃣ 内联模板

在使用组件时,写上特殊的特性:inline-template,就可以直接将里面的内容作为模板而不是被分发的内容(插槽)。
image.png
不过,inline-template 会让模板的作用域变得更加难以理解。所以作为最佳实践,请在组件内优先选择 template 选项或 .vue 文件里的一个