Vue 中用于处理组件边界情况的情况的 API:$root,$parent/$children,$refs,依赖注入 provide/ inject。
$root,$parent/$children,$refs 获取到的成员,都是响应式的,成员的值在本组件改变,在成员所属的实例内部也会改变。
依赖注入是非响应式的,如果试图改变 inject 传入的成员,Vue 会发出警告。依赖注入可以想象成可以多层穿透的 props。
$root
$root 能获取到 Vue 根实例上的成员。在项目规模小的情况下,可以把一些共享数据存储在根实例上,这样很方便。但是项目规模较大的时候,还是使用 Vuex 或自定义 store 来实现。
main.js:
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
data: {
title: '根实例 - Root'
},
methods: {
handle () {
console.log(this.title)
}
}
}).$mount('#app')
在组件中访问 Vue 根实例:
<template>
<div>
<!--
小型应用中可以在 vue 根实例里存储共享数据
组件中可以通过 $root 访问根实例
-->
$root.title:{{ $root.title }}
<br>
<button @click="$root.handle">获取 title</button>
<button @click="$root.title = 'Hello $root'">改变 title</button>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
$parent
$parent 可以访问到上一个父组件的成员。
缺点是当组件嵌套过多,想要访问到比较麻烦,而且程序出现问题的话,比较难以定位。
父组件:
<template>
<div class="parent">
parent
<child></child>
</div>
</template>
<script>
import child from './02-child'
export default {
components: {
child
},
data () {
return {
title: '获取父组件实例'
}
},
methods: {
handle () {
console.log(this.title)
}
}
}
</script>
<style>
.parent {
border: palegreen 1px solid;
}
</style>
child 组件:
<template>
<div class="child">
child<br>
$parent.title:{{ $parent.title }}<br>
<button @click="$parent.handle">获取 $parent.title</button>
<button @click="$parent.title = 'Hello $parent.title'">改变 $parent.title</button>
<grandson></grandson>
</div>
</template>
<script>
import grandson from './03-grandson'
export default {
components: {
grandson
}
}
</script>
<style>
.child {
border:paleturquoise 1px solid;
}
</style>
grandson 组件:
<template>
<div class="grandson">
grandson<br>
$parent.$parent.title:{{ $parent.$parent.title }}<br>
<button @click="$parent.$parent.handle">获取 $parent.$parent.title</button>
<button @click="$parent.$parent.title = 'Hello $parent.$parent.title'">改变 $parent.$parent.title</button>
</div>
</template>
<script>
export default {
}
</script>
<style>
.grandson {
border:navajowhite 1px solid;
}
</style>
$children
$children 获取到的是子组件数组,缺点是有时候只想找一个子组件,需要知道它的数组索引。
父组件:
<template>
<div>
<children1></children1>
<children2></children2>
<button @click="getChildren">获取子组件</button>
</div>
</template>
<script>
import children1 from './02-children1'
import children2 from './03-children2'
export default {
components: {
children1,
children2
},
methods: {
getChildren () {
console.log(this.$children)
console.log(this.$children[0].title)
console.log(this.$children[1].title)
this.$children[0].handle()
this.$children[1].handle()
}
}
}
</script>
<style>
</style>
chilren1组件:
<template>
<div>children1</div>
</template>
<script>
export default {
data () {
return {
title: 'children1 获取子组件 - title'
}
},
methods: {
handle () {
console.log(this.title)
}
}
}
</script>
$refs
如果给一个 html 元素赋予 ref 属性,那么获取到的 html 元素;如果给子组件赋予 ref 属性,那么获取到的是组件对象。
$refs 是要等到组件挂载之后才能访问到的,mounted 之前的生命周期函数访问不到。
在一些基于 Vue 的 ui 库中,验证表单会用到。
父组件:
<template>
<div>
<myinput ref="mytxt"></myinput>
<button @click="focus">获取焦点</button>
</div>
</template>
<script>
import myinput from './02-myinput'
export default {
components: {
myinput
},
methods: {
focus () {
this.$refs.mytxt.focus()
this.$refs.mytxt.value = 'hello'
}
}
// mounted () {
// this.$refs.mytxt.focus()
// }
}
</script>
<style>
</style>
子组件:
<template>
<div>
<input v-model="value" type="text" ref="txt">
</div>
</template>
<script>
export default {
data () {
return {
value: 'default'
}
},
methods: {
focus () {
this.$refs.txt.focus()
}
}
}
</script>
<style>
</style>
依赖注入 provide/inject
依赖注入在解决多层嵌套的时候使用到。父组件在provide
中提供自己的成员,子组件和嵌套在子组件中的组件通过inject
获取到父组件提供的成员。
子组件改变 inject 中的成员的值,在子组件内部可以看到修改后的效果,但是父组件的成员不会因此改变,并且 Vue 会打印警告信息,表示应该避免修改 inject 传入的成员。
父组件:
<template>
<div class="parent">
parent
<child></child>
</div>
</template>
<script>
import child from './02-child'
export default {
components: {
child
},
provide () {
return {
title: this.title,
handle: this.handle
}
},
data () {
return {
title: '父组件 provide'
}
},
methods: {
handle () {
console.log(this.title)
}
}
}
</script>
<style>
.parent {
border: palegreen 1px solid;
}
</style>
child 组件:
<template>
<div class="child">
child<br>
title:{{ title }}<br>
<button @click="handle">获取 title</button>
<button @click="title='xxx'">改变 title</button>
<grandson></grandson>
</div>
</template>
<script>
import grandson from './03-grandson'
export default {
components: {
grandson
},
inject: ['title', 'handle']
}
</script>
<style>
.child {
border:paleturquoise 1px solid;
}
</style>
grandson 组件:
<template>
<div class="grandson">
grandson<br>
title:{{ title }}<br>
<button @click="handle">获取 title</button>
<button @click="title='yyy'">改变 title</button>
</div>
</template>
<script>
export default {
inject: ['title', 'handle']
}
</script>
<style>
.grandson {
border:navajowhite 1px solid;
}
</style>
$attrs / $listeners
在开发组件的时候用到。
- $attrs
把父组件中非 prop 属性的成员绑定到内部组件 - $listeners
把父组件中的 DOM 对象的原生事件绑定到内部事件
$attrs
用来把父组件传的非 prop 属性的属性绑定到内部组件。
父组件给子组件传属性,大概有三种情况:
- 子组件中没有使用 props 接收,会自动设置到子组件最外层的标签上。比如 Vue 组件通常最外层是一个 div 元素,那么就会设置到这个 div 元素上。但是传过来的 class 和 style 并不遵循这个规则,它们总会合并最外层元素的 class 和 style,并且作为保留属性不能被 props 接收。
- 子组件中使用 props 接收成员,使用 props 接收的成员不会自动绑定到元素上。如果只接收一部分,剩余没有接收绑定到最外层元素。
- 使用 $attrs 接收不在 props 中的剩余属性,如果没有使用 props,那么接收除样式以外的所有属性。这个时候可以在元素上使用
v-bind="$attrs"
,这样会把接收到的属性展开赋予这个元素。注意,这个时候最外层元素也是会绑定 $attrs 中的属性,如果要禁止,组件的inheritAttrs
属性设置为 false,这样就只有使用 v-bind指定绑定 $attrs 的组件会继承父组件传过来的属性了。
父组件:
<template>
<div>
<myinput
required
placeholder="Enter your username"
class="theme-dark"
data-test="test">
</myinput>
</div>
</template>
<script>
import myinput from './02-myinput'
export default {
components: {
myinput
},
}
</script>
<style>
</style>
子组件:
<template>
<!--
1. 从父组件传给自定义子组件的属性,如果没有 prop 接收
会自动设置到子组件内部的最外层标签上
如果是 class 和 style 的话,会合并最外层标签的 class 和 style
-->
<!-- <input type="text" class="form-control" :placeholder="placeholder"> -->
<!--
2. 如果子组件中不想继承父组件传入的非 prop 属性,可以使用 inheritAttrs 禁用继承
然后通过 v-bind="$attrs" 把外部传入的非 prop 属性设置给希望的标签上
但是这不会改变 class 和 style
-->
<div>
<input type="text" v-bind="$attrs" class="form-control">
</div>
</template>
<script>
export default {
// props: ['placeholder', 'style', 'class']
// props: ['placeholder']
inheritAttrs: false
}
</script>
<style>
</style>
$listeners
子组件触发父组件传递的事件,有两种方式:
- 使用 $attrs 把事件绑定到要触发事件的元素上,使用
$emit
注册元素的事件。比如把父组件中传了一个方法onFocus
,要把它绑定给子组件中文本框的原生focus
方法,那么文本框的focus
可以这么写:@focus=$emit('onFocus', $event)"
。 - 直接使用
v-on="$listeners"
,这个会像 $attrs 把属性展开赋予元素一样,把事件展开赋予元素。需要注意的是,使用这种方法,父元素穿过来的事件就不能随便命名了,要和子组件的元素的原生事件名称一致,onFocus
改成focus
。
父组件:
<template>
<div>
<myinput
required
placeholder="Enter your username"
class="theme-dark"
@focus="onFocus"
@input="onInput"
data-test="test">
</myinput>
<button @click="handle">按钮</button>
</div>
</template>
<script>
import myinput from './02-myinput'
export default {
components: {
myinput
},
methods: {
handle () {
console.log(this.value)
},
onFocus (e) {
console.log(e)
},
onInput (e) {
console.log(e.target.value)
}
}
}
</script>
<style>
</style>
子组件:
<template>
<!--
3. 注册事件
-->
<!-- <div>
<input
type="text"
v-bind="$attrs"
class="form-control"
@focus="$emit('focus', $event)"
@input="$emit('input', $event)"
>
</div> -->
<!--
4. $listeners
-->
<div>
<input
type="text"
v-bind="$attrs"
class="form-control"
v-on="$listeners"
>
</div>
</template>
<script>
export default {
// props: ['placeholder', 'style', 'class']
// props: ['placeholder']
inheritAttrs: false
}
</script>
<style>
</style>
$emit
子组件可以使用 $emit 触发父组件的自定义事件。
子组件:
<template>
<div id="translate-form">
<form>
<input type="text" v-model="textToTranslate" placeholder="输入需要翻译的内容">
<select>
<option value="en">English</option>
</select>
<input type="submit" value="翻译" v-on:click="formSubmit">
</form>
</div>
</template>
<script>
export default {
name: 'TranslateForm',
data:function(){
return{
textToTranslate:'',
}
},
methods: {
formSubmit: function(e){
this.$emit('formSubmit', this.textToTranslate); //父组件监听的名字必须是formSubmit
e.preventDefault();
}
}
}
</script>
<style>
</style>
父组件:
<template>
<div id="app">
<h1>在线翻译</h1>
<h5>简单 / 易用 / 便捷</h5>
<TranslateForm v-on:formSubmit='translateText'></TranslateForm>
</div>
</template>
<script>
import TranslateForm from './components/TranslateForm'
export default {
name: 'App',
components:{
TranslateForm
},
methods:{
translateText:function(text){
alert(text)
}
}
}
</script>
<style>
#app {
text-align: center;
}
</style>
v-model
v-model 的本质还是父子组件通信:
1. 它会给子组件传递一个名字叫 value 的数据(Props)
2. 子组件默认监听 input 事件,修改绑定的数据(自定义事件)
3. 子组件通过 $emit 发布 input 事件。
父组件:
<input v-model="val" />
子组件:
<script>
export default {
props: {
value: { // 接收的就是父组件传过来的val
type: String
},
},
methods: {
handle () {
this.$emit('input', '123') // 触发父组件的 input 事件,把 val 的值改成 123
}
}
}
</script>