Vue 中用于处理组件边界情况的情况的 API:$root,$parent/$children,$refs,依赖注入 provide/ inject。
$root,$parent/$children,$refs 获取到的成员,都是响应式的,成员的值在本组件改变,在成员所属的实例内部也会改变。
依赖注入是非响应式的,如果试图改变 inject 传入的成员,Vue 会发出警告。依赖注入可以想象成可以多层穿透的 props。

$root

$root 能获取到 Vue 根实例上的成员。在项目规模小的情况下,可以把一些共享数据存储在根实例上,这样很方便。但是项目规模较大的时候,还是使用 Vuex 或自定义 store 来实现。

main.js:

  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. Vue.config.productionTip = false
  4. new Vue({
  5. render: h => h(App),
  6. data: {
  7. title: '根实例 - Root'
  8. },
  9. methods: {
  10. handle () {
  11. console.log(this.title)
  12. }
  13. }
  14. }).$mount('#app')

在组件中访问 Vue 根实例:

  1. <template>
  2. <div>
  3. <!--
  4. 小型应用中可以在 vue 根实例里存储共享数据
  5. 组件中可以通过 $root 访问根实例
  6. -->
  7. $root.title:{{ $root.title }}
  8. <br>
  9. <button @click="$root.handle">获取 title</button>&nbsp;&nbsp;
  10. <button @click="$root.title = 'Hello $root'">改变 title</button>
  11. </div>
  12. </template>
  13. <script>
  14. export default {
  15. }
  16. </script>
  17. <style>
  18. </style>

$parent

$parent 可以访问到上一个父组件的成员。
缺点是当组件嵌套过多,想要访问到比较麻烦,而且程序出现问题的话,比较难以定位。

父组件:

  1. <template>
  2. <div class="parent">
  3. parent
  4. <child></child>
  5. </div>
  6. </template>
  7. <script>
  8. import child from './02-child'
  9. export default {
  10. components: {
  11. child
  12. },
  13. data () {
  14. return {
  15. title: '获取父组件实例'
  16. }
  17. },
  18. methods: {
  19. handle () {
  20. console.log(this.title)
  21. }
  22. }
  23. }
  24. </script>
  25. <style>
  26. .parent {
  27. border: palegreen 1px solid;
  28. }
  29. </style>

child 组件:

  1. <template>
  2. <div class="child">
  3. child<br>
  4. $parent.title:{{ $parent.title }}<br>
  5. <button @click="$parent.handle">获取 $parent.title</button>
  6. <button @click="$parent.title = 'Hello $parent.title'">改变 $parent.title</button>
  7. <grandson></grandson>
  8. </div>
  9. </template>
  10. <script>
  11. import grandson from './03-grandson'
  12. export default {
  13. components: {
  14. grandson
  15. }
  16. }
  17. </script>
  18. <style>
  19. .child {
  20. border:paleturquoise 1px solid;
  21. }
  22. </style>

grandson 组件:

  1. <template>
  2. <div class="grandson">
  3. grandson<br>
  4. $parent.$parent.title:{{ $parent.$parent.title }}<br>
  5. <button @click="$parent.$parent.handle">获取 $parent.$parent.title</button>
  6. <button @click="$parent.$parent.title = 'Hello $parent.$parent.title'">改变 $parent.$parent.title</button>
  7. </div>
  8. </template>
  9. <script>
  10. export default {
  11. }
  12. </script>
  13. <style>
  14. .grandson {
  15. border:navajowhite 1px solid;
  16. }
  17. </style>

$children

$children 获取到的是子组件数组,缺点是有时候只想找一个子组件,需要知道它的数组索引。

父组件:

  1. <template>
  2. <div>
  3. <children1></children1>
  4. <children2></children2>
  5. <button @click="getChildren">获取子组件</button>
  6. </div>
  7. </template>
  8. <script>
  9. import children1 from './02-children1'
  10. import children2 from './03-children2'
  11. export default {
  12. components: {
  13. children1,
  14. children2
  15. },
  16. methods: {
  17. getChildren () {
  18. console.log(this.$children)
  19. console.log(this.$children[0].title)
  20. console.log(this.$children[1].title)
  21. this.$children[0].handle()
  22. this.$children[1].handle()
  23. }
  24. }
  25. }
  26. </script>
  27. <style>
  28. </style>

chilren1组件:

  1. <template>
  2. <div>children1</div>
  3. </template>
  4. <script>
  5. export default {
  6. data () {
  7. return {
  8. title: 'children1 获取子组件 - title'
  9. }
  10. },
  11. methods: {
  12. handle () {
  13. console.log(this.title)
  14. }
  15. }
  16. }
  17. </script>

$refs

如果给一个 html 元素赋予 ref 属性,那么获取到的 html 元素;如果给子组件赋予 ref 属性,那么获取到的是组件对象。
$refs 是要等到组件挂载之后才能访问到的,mounted 之前的生命周期函数访问不到。
在一些基于 Vue 的 ui 库中,验证表单会用到。
image.png

父组件:

  1. <template>
  2. <div>
  3. <myinput ref="mytxt"></myinput>
  4. <button @click="focus">获取焦点</button>
  5. </div>
  6. </template>
  7. <script>
  8. import myinput from './02-myinput'
  9. export default {
  10. components: {
  11. myinput
  12. },
  13. methods: {
  14. focus () {
  15. this.$refs.mytxt.focus()
  16. this.$refs.mytxt.value = 'hello'
  17. }
  18. }
  19. // mounted () {
  20. // this.$refs.mytxt.focus()
  21. // }
  22. }
  23. </script>
  24. <style>
  25. </style>

子组件:

  1. <template>
  2. <div>
  3. <input v-model="value" type="text" ref="txt">
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. data () {
  9. return {
  10. value: 'default'
  11. }
  12. },
  13. methods: {
  14. focus () {
  15. this.$refs.txt.focus()
  16. }
  17. }
  18. }
  19. </script>
  20. <style>
  21. </style>

依赖注入 provide/inject

依赖注入在解决多层嵌套的时候使用到。父组件在provide中提供自己的成员,子组件和嵌套在子组件中的组件通过inject获取到父组件提供的成员。
子组件改变 inject 中的成员的值,在子组件内部可以看到修改后的效果,但是父组件的成员不会因此改变,并且 Vue 会打印警告信息,表示应该避免修改 inject 传入的成员。

父组件:

  1. <template>
  2. <div class="parent">
  3. parent
  4. <child></child>
  5. </div>
  6. </template>
  7. <script>
  8. import child from './02-child'
  9. export default {
  10. components: {
  11. child
  12. },
  13. provide () {
  14. return {
  15. title: this.title,
  16. handle: this.handle
  17. }
  18. },
  19. data () {
  20. return {
  21. title: '父组件 provide'
  22. }
  23. },
  24. methods: {
  25. handle () {
  26. console.log(this.title)
  27. }
  28. }
  29. }
  30. </script>
  31. <style>
  32. .parent {
  33. border: palegreen 1px solid;
  34. }
  35. </style>

child 组件:

  1. <template>
  2. <div class="child">
  3. child<br>
  4. title:{{ title }}<br>
  5. <button @click="handle">获取 title</button>
  6. <button @click="title='xxx'">改变 title</button>
  7. <grandson></grandson>
  8. </div>
  9. </template>
  10. <script>
  11. import grandson from './03-grandson'
  12. export default {
  13. components: {
  14. grandson
  15. },
  16. inject: ['title', 'handle']
  17. }
  18. </script>
  19. <style>
  20. .child {
  21. border:paleturquoise 1px solid;
  22. }
  23. </style>

grandson 组件:

  1. <template>
  2. <div class="grandson">
  3. grandson<br>
  4. title:{{ title }}<br>
  5. <button @click="handle">获取 title</button>
  6. <button @click="title='yyy'">改变 title</button>
  7. </div>
  8. </template>
  9. <script>
  10. export default {
  11. inject: ['title', 'handle']
  12. }
  13. </script>
  14. <style>
  15. .grandson {
  16. border:navajowhite 1px solid;
  17. }
  18. </style>

$attrs / $listeners

在开发组件的时候用到。

  • $attrs
    把父组件中非 prop 属性的成员绑定到内部组件
  • $listeners
    把父组件中的 DOM 对象的原生事件绑定到内部事件

$attrs

用来把父组件传的非 prop 属性的属性绑定到内部组件。

父组件给子组件传属性,大概有三种情况:

  1. 子组件中没有使用 props 接收,会自动设置到子组件最外层的标签上。比如 Vue 组件通常最外层是一个 div 元素,那么就会设置到这个 div 元素上。但是传过来的 class 和 style 并不遵循这个规则,它们总会合并最外层元素的 class 和 style,并且作为保留属性不能被 props 接收。
  2. 子组件中使用 props 接收成员,使用 props 接收的成员不会自动绑定到元素上。如果只接收一部分,剩余没有接收绑定到最外层元素。
  3. 使用 $attrs 接收不在 props 中的剩余属性,如果没有使用 props,那么接收除样式以外的所有属性。这个时候可以在元素上使用v-bind="$attrs",这样会把接收到的属性展开赋予这个元素。注意,这个时候最外层元素也是会绑定 $attrs 中的属性,如果要禁止,组件的inheritAttrs属性设置为 false,这样就只有使用 v-bind指定绑定 $attrs 的组件会继承父组件传过来的属性了。

父组件:

  1. <template>
  2. <div>
  3. <myinput
  4. required
  5. placeholder="Enter your username"
  6. class="theme-dark"
  7. data-test="test">
  8. </myinput>
  9. </div>
  10. </template>
  11. <script>
  12. import myinput from './02-myinput'
  13. export default {
  14. components: {
  15. myinput
  16. },
  17. }
  18. </script>
  19. <style>
  20. </style>

子组件:

  1. <template>
  2. <!--
  3. 1. 从父组件传给自定义子组件的属性,如果没有 prop 接收
  4. 会自动设置到子组件内部的最外层标签上
  5. 如果是 class 和 style 的话,会合并最外层标签的 class 和 style
  6. -->
  7. <!-- <input type="text" class="form-control" :placeholder="placeholder"> -->
  8. <!--
  9. 2. 如果子组件中不想继承父组件传入的非 prop 属性,可以使用 inheritAttrs 禁用继承
  10. 然后通过 v-bind="$attrs" 把外部传入的非 prop 属性设置给希望的标签上
  11. 但是这不会改变 class 和 style
  12. -->
  13. <div>
  14. <input type="text" v-bind="$attrs" class="form-control">
  15. </div>
  16. </template>
  17. <script>
  18. export default {
  19. // props: ['placeholder', 'style', 'class']
  20. // props: ['placeholder']
  21. inheritAttrs: false
  22. }
  23. </script>
  24. <style>
  25. </style>

$listeners

子组件触发父组件传递的事件,有两种方式:

  1. 使用 $attrs 把事件绑定到要触发事件的元素上,使用$emit注册元素的事件。比如把父组件中传了一个方法onFocus ,要把它绑定给子组件中文本框的原生 focus 方法,那么文本框的focus可以这么写: @focus=$emit('onFocus', $event)"
  2. 直接使用v-on="$listeners",这个会像 $attrs 把属性展开赋予元素一样,把事件展开赋予元素。需要注意的是,使用这种方法,父元素穿过来的事件就不能随便命名了,要和子组件的元素的原生事件名称一致,onFocus改成focus

父组件:

  1. <template>
  2. <div>
  3. <myinput
  4. required
  5. placeholder="Enter your username"
  6. class="theme-dark"
  7. @focus="onFocus"
  8. @input="onInput"
  9. data-test="test">
  10. </myinput>
  11. <button @click="handle">按钮</button>
  12. </div>
  13. </template>
  14. <script>
  15. import myinput from './02-myinput'
  16. export default {
  17. components: {
  18. myinput
  19. },
  20. methods: {
  21. handle () {
  22. console.log(this.value)
  23. },
  24. onFocus (e) {
  25. console.log(e)
  26. },
  27. onInput (e) {
  28. console.log(e.target.value)
  29. }
  30. }
  31. }
  32. </script>
  33. <style>
  34. </style>

子组件:

  1. <template>
  2. <!--
  3. 3. 注册事件
  4. -->
  5. <!-- <div>
  6. <input
  7. type="text"
  8. v-bind="$attrs"
  9. class="form-control"
  10. @focus="$emit('focus', $event)"
  11. @input="$emit('input', $event)"
  12. >
  13. </div> -->
  14. <!--
  15. 4. $listeners
  16. -->
  17. <div>
  18. <input
  19. type="text"
  20. v-bind="$attrs"
  21. class="form-control"
  22. v-on="$listeners"
  23. >
  24. </div>
  25. </template>
  26. <script>
  27. export default {
  28. // props: ['placeholder', 'style', 'class']
  29. // props: ['placeholder']
  30. inheritAttrs: false
  31. }
  32. </script>
  33. <style>
  34. </style>

$emit

子组件可以使用 $emit 触发父组件的自定义事件。
子组件:

  1. <template>
  2. <div id="translate-form">
  3. <form>
  4. <input type="text" v-model="textToTranslate" placeholder="输入需要翻译的内容">
  5. <select>
  6. <option value="en">English</option>
  7. </select>
  8. <input type="submit" value="翻译" v-on:click="formSubmit">
  9. </form>
  10. </div>
  11. </template>
  12. <script>
  13. export default {
  14. name: 'TranslateForm',
  15. data:function(){
  16. return{
  17. textToTranslate:'',
  18. }
  19. },
  20. methods: {
  21. formSubmit: function(e){
  22. this.$emit('formSubmit', this.textToTranslate); //父组件监听的名字必须是formSubmit
  23. e.preventDefault();
  24. }
  25. }
  26. }
  27. </script>
  28. <style>
  29. </style>

父组件:

  1. <template>
  2. <div id="app">
  3. <h1>在线翻译</h1>
  4. <h5>简单 / 易用 / 便捷</h5>
  5. <TranslateForm v-on:formSubmit='translateText'></TranslateForm>
  6. </div>
  7. </template>
  8. <script>
  9. import TranslateForm from './components/TranslateForm'
  10. export default {
  11. name: 'App',
  12. components:{
  13. TranslateForm
  14. },
  15. methods:{
  16. translateText:function(text){
  17. alert(text)
  18. }
  19. }
  20. }
  21. </script>
  22. <style>
  23. #app {
  24. text-align: center;
  25. }
  26. </style>

v-model

v-model 的本质还是父子组件通信:
1. 它会给子组件传递一个名字叫 value 的数据(Props)
2. 子组件默认监听 input 事件,修改绑定的数据(自定义事件)
3. 子组件通过 $emit 发布 input 事件。

父组件:

  1. <input v-model="val" />

子组件:

  1. <script>
  2. export default {
  3. props: {
  4. value: { // 接收的就是父组件传过来的val
  5. type: String
  6. },
  7. },
  8. methods: {
  9. handle () {
  10. this.$emit('input', '123') // 触发父组件的 input 事件,把 val 的值改成 123
  11. }
  12. }
  13. }
  14. </script>