Element & Component Access
(访问元素 & 组件 ) 在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。
Accessing the Root Instance
(访问根实例
)
In every subcomponent of anew Vueinstance, this root instance can be accessed with the $root property. For example, in this root instance:
在每个 new Vue 实例的子组件中,其根实例可以通过 $root property 进行访问。例如,在这个根实例中:
// The root Vue instance
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
All subcomponents will now be able to access this instance and use it as a global store:
// Get root data
this.$root.foo
// Set root data
this.$root.foo = 2
// Access root computed properties
this.$root.bar
// Call root methods
this.$root.baz()
对于 demo 或非常小型的有少量组件的应用来说这是很方便的。不过这个模式扩展到中大型应用来说就不然了。因此在绝大多数情况下,我们强烈推荐使用Vuex来管理应用的状态。
Accessing the Parent Component Instance(访问父级组件实例
)
Similar to$root, the$parentproperty can be used to access the parent instance from a child. This can be tempting to reach for as a lazy alternative to passing data with a prop.
和$root类似,$parentproperty 可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。
In most cases, reaching into the parent makes your application more difficult to debug and understand, especially if you mutate data in the parent. When looking at that component later, it will be very difficult to figure out where that mutation came from. 在绝大多数情况下,触达父级组件会使得你的应用更难调试和理解,尤其是当你变更了父级组件的数据的时候。当我们稍后回看那个组件的时候,很难找出那个变更是从哪里发起的。
Accessing Child Component Instances & Child Elements
Despite the existence of props and events, sometimes you might still need to directly access a child component in JavaScript. To achieve this you can assign a reference ID to the child component using the ref attribute. For example:
尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref 这个 attribute 为子组件赋予一个 ID 引用。例如:
<base-input ref="usernameInput"></base-input>
现在在你已经定义了这个 ref 的组件里,你可以使用:
this.$refs.usernameInput
当 ref 和 v-for 一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。
$refs只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问$refs。
Dependency Injection(依赖注入)
使用$parentproperty 无法很好的扩展到更深层级的嵌套组件上。这也是依赖注入的用武之地,它用到了两个新的实例选项:provide和inject。
provide选项允许我们指定我们想要提供给后代组件的数据/方法。
provide: function () {
return {
getMap: this.getMap
}
}
然后在任何后代组件里,我们都可以使用inject选项来接收指定的我们想要添加在这个实例上的 property:
inject: ['getMap']
相比$parent来说,这个用法可以让我们在任意 后代组件中访问getMap,而不需要暴露整个
In fact, you can think of dependency injection as sort of “long-range props”, except:
- ancestor components don’t need to know which descendants use the properties it provides
- descendant components don’t need to know where injected properties are coming from
实际上,你可以把依赖注入看作一部分“大范围有效的 prop”,除了:
- 祖先组件不需要知道哪些后代组件使用它提供的 property
- 后代组件不需要知道被注入的 property 来自哪里
然而,依赖注入还是有负面影响的。它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。同时所提供的 property 是非响应式的。这是出于设计的考虑,因为使用它们来创建一个中心化规模化的数据跟使用$root做这件事都是不够好的。如果你想要共享的这个 property 是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像Vuex这样真正的状态管理方案了。
Programmatic Event Listeners
现在,你已经知道了 $emit 的用法,它可以被 v-on 侦听,但是 Vue 实例同时在其事件接口中提供了其它的方法。我们可以:
- 通过$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()
}
这里有两个潜在的问题:
- 它需要在这个组件实例中保存这个picker,如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。
- 我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化地清理我们建立的所有东西。
你应该通过一个程序化的侦听器解决这两个问题:
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()
})
}
}
注意,即便如此,如果你发现自己不得不在单个组件里做很多建立和清理的工作,最好的方式通常还是创建更多的模块化组件。在这个例子中,我们推荐创建一个可复用的
注意 Vue 的事件系统不同于浏览器的EventTarget API。尽管它们工作起来是相似的,但是$emit、$on, 和$off并不是dispatchEvent、addEventListener和removeEventListener的别名。
Circular References
Recursive Components
(递归组件 ) 组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事:
name: 'unique-name-of-my-component'
当你使用Vue.component全局注册一个组件时,这个全局的 ID 会自动设置为该组件的name选项。
Vue.component('unique-name-of-my-component', {
// ...
})
稍有不慎,递归组件就可能导致无限循环:
name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'
类似上述的组件将会导致“max stack size exceeded”错误,所以请确保递归调用是条件性的 (例如使用一个最终会得到 false 的 v-if)。
Circular References Between Components(组件之间的循环引用
) Let’s say you’re building a file directory tree, like in Finder or File Explorer. You might have a tree-folder component with this template:
<p>
<span>{{ folder.name }}</span>
<tree-folder-contents :children="folder.children"/>
</p>
Then a tree-folder-contents component with this template:
<ul>
<li v-for="child in children">
<tree-folder v-if="child.children" :folder="child"/>
<span v-else>{{ child.name }}</span>
</li>
</ul>
When you look closely, you’ll see that these components will actually be each other’s descendentand_ancestor in the render tree - a paradox! When registering components globally withVue.component, this paradox is resolved for you automatically. If that’s you, you can stop reading here.
当你仔细观察的时候,你会发现这些组件在渲染树中互为对方的后代和_祖先——一个悖论!当通过Vue.component全局注册组件的时候,这个悖论会被自动解开。如果你是这样做的,那么你可以跳过这里。
然而,如果你使用一个模块系统依赖/导入组件,例如通过 webpack 或 Browserify,你会遇到一个错误:
Failed to mount component: template or render function not defined.
为了解释这里发生了什么,我们先把两个组件称为 A 和 B。模块系统发现它需要 A,但是首先 A 依赖 B,但是 B 又依赖 A,但是 A 又依赖 B,如此往复。这变成了一个循环,不知道如何不经过其中一个组件而完全解析出另一个组件。为了解决这个问题,我们需要给模块系统一个点,在那里“A反正是需要 B 的,但是我们不需要先解析 B。”
在我们的例子中,把
beforeCreate: function () {
this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}
或者,在本地注册组件的时候,你可以使用 webpack 的异步import:
components: {
TreeFolderContents: () => import('./tree-folder-contents.vue')
}
Alternate Template Definitions(模板定义的替代品
内联模板 当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 文件里的一个元素来定义模板。
X-Template
另一个定义模板的方式是在一个