vue.js组件的通信:provide/inject

256 阅读3分钟

组件的通信

一般来说组件可以有以下几种关系:

1gkP1A.png
A 和 B是父子关系,B、C、D 是兄弟关系,A 和 E是隔代关系(有可能隔好多代)。

Vue.js 可以使用内置的方法来访问组件。

  • ref: 用于引用元素或组件。
  • $parent/$children: 访问父组件或子组件。

使用以上方法访问都可以获取组件实例,可以直接调用组件的方法或访问数据。比如下面的示例中使用ref来访问组件: (部分代码省略)

// fButton组件
<template>
  <button class="f-button" :class="[type, iconPosition, size]" @click="onClick">
    <span class="word">
      <slot>按钮</slot>
    </span>
    <f-icon :iconName="icon" v-if="icon && !loading"></f-icon>
    <f-icon class="loading" iconName="loading" v-if="loading"></f-icon>
  </button>
</template>
<script type="text/javascript">
import fIcon from "./Icon";
export default {
  name: "fButton",
  data() {
    return {
      information: 'hello'
    };
  },
  props: {
    type: {
      default: "default",
      type: String,
      validator(val) {
        return ["default", "dashed", "danger", "primary"].indexOf(val) >= 0;
      }
    },
    icon: {},
    iconPosition: {
      default: "left",
      type: String,
      validator(val) {
        return ["left", "right"].indexOf(val) >= 0;
      }
    },
    loading: {
      type: Boolean,
      default: false
    },
    size: {
      default: "medium",
      type: String,
      validator(val) {
        return ["medium", "small", "large"].indexOf(val) >= 0;
      }
    }
  },
  components: {
    fIcon
  },
  methods: {
    onClick() {
        this.$emit('click')
    }
  }
};
</script>

<template>
  <div id="app">
    <section>
      <f-button icon="loading" iconPosition="right" ref="fbutton">Default</f-button>
    </section>
  </div>
</template>
<script>
  import fButton from './components/Button'
  export default {
    name: 'App',
    data() {
      return {
        message: 'hello'
      }
    },
    components: {
      fButton,
    }
    mounted() {
      console.log(this.$refs.fbutton)  //获取到fButton组件实例
      console.log(this.$refs.fbutton.information)  //hello
    }
  }
</script>

使用 $parent/$children 可以访问到父组件实例或子组件实例,基于当前上下文访问父组件或全部子组件。

但是以上两种方法无法在跨级兄弟组件之间通信,例如下面的结构:

App.vue
<f-button icon="loading" iconPosition="right" ref="fbutton">Default</f-button>
<f-input size="small" placeholder="请输入内容" :disabled="true"></f-input>

想要在 f-button 组件中访问到引用它的页面 App.vue 中的另一个兄弟组件 f-input,就无法使用上面的两种方法了,得使用 Vuex 和 Bus 这样的解决方案。

provide/inject

前面讲到 ref 和 $parent / $children 在跨级通信时是有弊端的。当组件 A 和组件 B 中间隔了数代(甚⾄不确定具体级别)时,以往会借助 Vuex 或 Bus 这样的解决⽅案,不得不引⼊ 三⽅库来⽀持。但也可以使用 Vue.js 内置的 provide / inject 接⼝。

什么是 provide/inject ?

查看Vue.js官方文档可以知道: cn.vuejs.org/v2/api/#pro…

provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。

下面来看看该 API 的使用方法:

//input.vue 为 App.vue 的子组件
// App.vue
export default {
    provide () {
        return {
            information: 'hello'
        }
    }
}
// input.vue
export default {
    inject: ['information'],
    mounted() {
        console.log(this.information) //hello
    }
}

可以看到,在 App.vue ⾥,我们设置了⼀个 provide: information,值为 hello,它的作⽤就是将 information 这个变量提供给它的所有⼦组件。 ⽽在 input.vue 中,通过 inject 注⼊了从 App 组件中提供的 information 变 量,那么在组件 input 中,就可以直接通过 this.information 访问这个变量 了,它的值也是 hello。这就是 provide / inject API 最核⼼的⽤ 法。

需要注意的是:

provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的

所以,上⾯ App.vue 的 information 如果改变了,input.vue 的 this.information 是不 会改变的,仍然是 hello。

只要⼀个组件使⽤了 provide 向下提供数据,那其下所有的⼦组件都可以通过inject来注⼊,不管中间隔了多少代,⽽且可以注⼊多个来⾃不同⽗级提供的数据。需要注意的是,⼀旦注⼊了某个数据,⽐如上⾯示例中的 information,那这个组件中就不能再声明 information 这个数据了,因为它已经被⽗级占有。

总结: provide / inject 在组件跨级通信时非常有用(无论是隔了多少代)。