1️⃣ 组件边界情况
这里记录的都是和处理边界情况有关的功能,即一些需要对 Vue 的规则做一些小调整的特殊情况。不过注意这些功能都是有劣势或危险的场景的。我们会在每个案例中注明,所以当你使用每个功能的时候请稍加留意。
1️⃣ 访问元素 & 组件
在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的。
2️⃣ $root - 访问根实例
在每个 new Vue 实例的子组件中,子组件可以通过 $root property 进行访问根实例的数据。
注意:对于 demo 或非常小型的有少量组件的应用来说这是很方便的。不过这个模式扩展到中大型应用来说就不然了。因此在绝大多数情况下,我们强烈推荐使用 Vuex 来管理应用的状态。
// 在任何一个子组件使用都会访问到跟组件的数据
this.$root.msg
2️⃣ $parent - 访问父级组件实例
和 $root 类似,$parent property 可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。
注意:在绝大多数情况下,触达父级组件会使得你的应用更难调试和理解,尤其是当你变更了父级组件的数据的时候。当我们稍后回看那个组件的时候,很难找出那个变更是从哪里发起的。
2️⃣ provide 和 inject 依赖注入
在上面的例子中,利用 $parent 属性,没有办法很好的扩展到更深层级的嵌套组件上。这也是依赖注入的用武之地,它用到了两个新的实例选项:provide 和 inject。
1. provide 选项允许我们指定想要提供给后代组件的数据/方法
2. 然后再任何后代组件中,我们都可以使用 inject 选项来接收指定想要添加在实例上的属性
实际上,你可以把依赖注入看作一部分“大范围有效的 prop”,除了:
1. 祖先组件不需要知道哪些后代组件使用它提供的 property
2. 后代组件不需要知道被注入的 property 来自哪里
然而,依赖注入还是有负面影响的。它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。同时所提供的 property 是非响应式的。这是出于设计的考虑,因为使用它们来创建一个中心化规模化的数据跟使用 $root 做这件事都是不够好的。如果你想要共享的这个 property 是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像 Vuex 这样真正的状态管理方案了。
2️⃣ ref - 访问子组件实例或子元素
尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref 这个 attribute 为子组件赋予一个 ID 引用。例如:
1. ref:将子组件的实例或子元素添加到父组件的 $refs 中
2. $ref:包含了所有子组件或子元素的 ref 列表
当 ref
和 v-for
一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。
$refs
只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”—— 你应该避免在模板或计算属性中访问$refs
。
1️⃣ 程序化的事件侦听器
现在,你已经知道了 $emit 的用法,它可以被 v-on 侦听,但是 Vue 实例同时在其事件接口中提供了其它的方法。我们可以:
1. 通过 $on(eventName, eventHandler) 侦听一个事件
2. 通过 $once(eventName, eventHandler) 只监听一次
3. 通过 $off(eventName, eventHandler) 停止侦听一个事件
注意 Vue 的事件系统不同于浏览器的 EventTarget API。尽管它们工作起来是相似的,但是 $emit、$on, 和 $off 并不是 dispatchEvent、addEventListener 和 removeEventListener 的别名。
2️⃣ $on
1️⃣ 循环引用
2️⃣ 递归组件
递归组件很容造成栈溢出, 所有使用递归组件时, 要注意有出口.
组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事:
export default {
// 组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事:
// 当要递归组件时要给组件一个名字 否则将要报错
name: "ComponentOne",
};
当你使用 Vue.component 全局注册一个组件时,这个全局的 ID 会自动设置为该组件的 name 选项。
Vue.component('unique-name-of-my-component', {
// ...
})
使用示例
<!-- >>>>>>>>>> App.vue >>>>>>>>>> -->
<template>
<div id="app">
<component-one :list="list"></component-one>
</div>
</template>
<script>
import ComponentOne from "./components/ComponentOne.vue";
export default {
components: { ComponentOne },
data() {
return {
list: [
{ name: "A" },
{
name: "B",
arr: [
{ name: "B1-1" },
{
name: "B1-2",
arr: [{ name: "B2-1" }, { name: "B2-2" }, { name: "B2-3" }],
},
{ name: "B1-3" },
],
},
{ name: "C" },
],
};
},
};
</script>
<!-- >>>>>>>>>> ComponentOne.vue >>>>>>>>>> -->
<template>
<ul class="co">
<li v-for="(item, index) in list" :key="index">
{{ item.name }}
// 调用自己
<componentOne :list="item.arr"></componentOne>
</li>
</ul>
</template>
<script>
export default {
// 组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事:
// 当要递归组件时要给组件一个名字 否则将要报错
name: "ComponentOne",
props: {
list: {
type: Array,
default: () => [],
},
},
};
</script>
2️⃣ 组件之间的循环引用
有时,在去构建一些组件时,会出现组件互为对方的后代/祖先:
Vue.component("cmp-a", {
template: `
<div>
<cmp-b></cmp-b>
</div>
`,
});
Vue.component("cmp-b", {
template: `
<div>
<cmp-a></cmp-a>
</div>
`,
});
此时,我们使用的是全局注册组件,并不会出现悖论,但是如果使用的为局部组件就会出现悖论。
但是即使用了全局注册组件,在使用 webpack 去导入组件时,也会出现一个错误:Failed to mount component: template or render function not defined。
模块系统发现它需要 A,但是首先 A 依赖 B,但是 B 又依赖 A,但是 A 又依赖 B,如此往复。这变成了一个循环,不知道如何不经过其中一个组件而完全解析出另一个组件。为了解决这个问题,我们需要给模块系统一个点:“A 反正是需要 B 的,但是我们不需要先解析 B。”
beforeCreate () {
this.$options.components.CmpB = require('./tree-folder-contents.vue').default;
}
或者,在本地注册组件的时候,你可以使用 webpack 的异步 import:
components: {
CmpB: () => import("./tree-folder-contents.vue");
}
1️⃣ 模板定义的替代品
2️⃣ 内联模板
在使用组件时,写上特殊的特性:inline-template,就可以直接将里面的内容作为模板而不是被分发的内容(插槽)。 另一个定义模板的方式是在一个
不过,inline-template 会让模板的作用域变得更加难以理解。所以作为最佳实践,请在组件内优先选择 template 选项或 .vue 文件里的一个 元素来定义模板。
2️⃣ X-Template