访问元素 & 组件
访问根实例
在每个 new Vue 实例的子组件中,其根实例可以通过 $root property 进行访问。例如,在这个根实例中:
// Vue 根实例
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
所有的子组件都可以将这个实例作为一个全局 store 来访问或使用
// 获取根组件的数据
this.$root.foo
// 写入根组件的数据
this.$root.foo = 2
// 访问根组件的计算属性
this.$root.bar
// 调用根组件的方法
this.$root.baz()
访问父级组件实例(不推荐)
和 $root 类似,$parent property 可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。
在绝大多数情况下,触达父级组件会使得你的应用更难调试和理解,尤其是当你变更了父级组件的数据的时候。当我们稍后回看那个组件的时候,很难找出那个变更是从哪里发起的。
访问子组件实例或子元素
通过 ref 这个 attribute 为子组件赋予一个 ID 引用。
<base-input ref="usernameInput"></base-input>
父级组件定义方法调用子组件的ref:
methods: {
// 用来从父级组件聚焦输入框
focus: function () {
this.$refs.input.focus()
}
}
当 ref 和 v-for 一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。
$refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs。
依赖注入
provide 选项允许我们指定我们想要提供给后代组件的数据/方法。
provide: function () {
return {
getMap: this.getMap
}
}
然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的 property:
inject: ['getMap']
程序化的事件侦听器
官方API:点我
- 通过 $on(eventName, eventHandler) 侦听一个事件
- 通过 $once(eventName, eventHandler) 一次性侦听一个事件
- 通过 $off(eventName, eventHandler) 停止侦听一个事件
// 一次性将这个日期选择器附加到一个输入框上
// 它会被挂载到 DOM 上。
mounted: function () {
// Pikaday 是一个第三方日期选择器的库
this.picker = new Pikaday({
field: this.$refs.input,
format: 'YYYY-MM-DD'
})
},
// 在组件被销毁之前,
// 也销毁这个日期选择器。
beforeDestroy: function () {
this.picker.destroy()
}
// 使用v-once实现的方式
mounted: function () {
var picker = new Pikaday({
field: this.$refs.input,
format: 'YYYY-MM-DD'
})
this.$once('hook:beforeDestroy', function () {
picker.destroy()
})
}
注意 Vue 的事件系统不同于浏览器的 EventTarget API。尽管它们工作起来是相似的,但是 e m i t 、 emit、 emit、on, 和 $off 并不是 dispatchEvent、addEventListener 和 removeEventListener 的别名。
循环引用
递归组件
组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事,当你使用 Vue.component 全局注册一个组件时,这个全局的 ID 会自动设置为该组件的 name 选项。
Vue.component('unique-name-of-my-component', {
// ...
})
为了避免组件递归调用导致内存泄露,请在渲染的过程中加入合适判断逻辑,实现有条件的渲染(比如使用v-if)。
组件之间的循环引用
解决组件之间的循环引用,常用的两种方式。
- 命周期钩子 beforeCreate 时去注册它
- 在本地注册组件的时候,你可以使用 webpack 的异步 import:
// import 引入
components: {
TreeFolderContents: () => import('./tree-folder-contents.vue')
}
// beforeCreate
beforeCreate: function () {
this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}
模板定义的替代品
内联模板(不推荐)
当 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>
不过,inline-template 会让模板的作用域变得更加难以理解。所以作为最佳实践,请在组件内优先选择 template 选项或 .vue 文件里的一个 <template> 元素来定义模板。
X-Template(不推荐)
另一个定义模板的方式是在一个
定义一个模板
<script type="text/x-template" id="hello-world-template">
<p>Hello hello hello</p>
</script>
使用这个模板
Vue.component('hello-world', {
template: '#hello-world-template'
})
控制更新
强制更新
迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
vm.$forceUpdate()
通过 v-once 创建低开销的静态组件
添加 v-once attribute 以确保这些内容只计算一次然后缓存起来,就像这样:
Vue.component('terms-of-service', {
template: `
<div v-once>
<h1>Terms of Service</h1>
... a lot of static content ...
</div>
`
})
当你需要渲染大量静态内容时,极少数的情况下它会给你带来便利,除非你非常留意渲染变慢了,不然它完全是没有必要的——再加上它在后期会带来很多困惑。例如,设想另一个开发者并不熟悉 v-once 或漏看了它在模板中,他们可能会花很多个小时去找出模板为什么无法正确更新。