Vue跨组件可响应通信【provide / inject】

目录

1. Vue 组件之间常用的通信方式
2. provide / inject 基本使用
3. provide / inject 可响应通信
4. provide / inject 替代 Vuex
5. 进阶技巧
6. 结语


1. Vue 组件之间常用的通信方式

Vue 组件之间常用的通信方式有:

  • props 属性传递数据
  • 自定义事件 event
    a. 全局的eventHub机制
    b. 父组件调用子组件使用 @eventName="handleFunc",子组件在需要的时候调用 this.$emit(eventName, params) 即可通过 params 传参。
  • ref 给元素或组件注册引用信息,然后父组件通过 this.$refs.child 获取到子组件实例对象。 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素。
  • $parent / $children:访问父 / 子实例。

但以上这些方法一般只适合在父子组件中通信,无法在跨级或兄弟间通信,或者要实现跨级通信会很麻烦,需要一层一层的传递。

2. provide / inject 基本使用

我们知道,在做 Vue 大型项目时,可以使用 Vuex 做状态管理来突破跨组件的数据共享,它是一个专为 Vue.js 开发的状态管理模式,用于集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

那不使用 Vuex 的情况下,我们能否有其他跨组件的通信方式呢?答案是肯定有的,这就需要Vue内置的 provide / inject 接口,它可以实现无依赖的组件通信方法,类似于react的 Context共享数据, 父组件 与【子孙】组件而不只是子组件共享数据,可以跨层级。

provide / inject 是 Vue.js 2.2.0 版本后新增的 API,在文档中这样介绍 :

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

并且文档中有如下提示:

provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

看不懂上面的介绍没有关系,不过上面的这句提示应该明白,就是说 Vue.js 不建议在业务中使用这对 API,而是在插件 / 组件库中使用。不过建议归建议,如果你用好了,这个 API 会非常有用。

先来看一下这个 API 怎么用,假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件。

// A.vue
export default {
  provide: {
    name: 'Spring'
  }
}

// B.vue
export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // Spring
  }
}

可以看到,在 A.vue 里,我们设置了一个 provide: name,值为 Spring,它的作用就是将 name 这个变量提供给它的所有子组件。而在 B.vue 中,通过 inject 注入了从 A 组件中提供的 name 变量,那么在组件 B 中,就可以直接通过 this.name 访问这个变量了,它的值也是 Spring。这就是 provide / inject API 最基本的用法。

需要注意的是:

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

所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 Spring。

那如何才能实现 可响应 的通信呢?

3. provide / inject 可响应通信

下面介绍使用方法:
1、在父组件中给 provide 选项返回一个对象或对象函数:

<template>
    <div>
        <input v-model="staticValue" />
        <input v-model="staticObject.value" />
        <children />
    </div>
</template>

<script>
	import Children from "./components/Children.vue";
	export default {
	    name: "Parent",
	    data() {
	        return {
	            staticValue: "func spring",
	            staticObject: {value: "obj spring"}
	        };
	    },
	    provide() {
	        return {
	            staticValue: this.staticValue, // 直接返回值,不可响应
	            staticObject: this.staticObject, // 返回一个对象,可响应
	            getReactiveValue: () => this.staticValue // 返回一个对象的函数,可响应
	        }
	    },
	    components: {
	    	Children
	    }
</script>

2、在子组件中(单纯的演示,只做衔接父组件和孙组件):

<template>
    <div>
        <grand-son />
    </div>
</template>

<script>
import GrandSon from "./components/GrandSon.vue";
export default {
    name: "Children",
    data() {
        return {};
    },
    components: {
    	GrandSon
    }
};
</script>

3、在孙组件中:

<template>
    <div>
        <h2>staticValue:{{ staticValue }}</h2>
        <h2>reactiveFuncValue: {{ reactiveValue }}</h2>
        <h2>reactiveObjValue:{{ staticObject.value }}</h2>
    </div>
</template>

<script>
export default {
	name: "GrandSon",
    inject: ["staticValue", "getReactiveValue", "staticObject"],
    computed: {
        reactiveValue() {
            return this.getReactiveValue(); // 返回注入的对象函数,通过计算属性来监听值的变化
        },
    }
};
</script>

这样子我们就是实现了跨组件的可响应通信:
在这里插入图片描述
4. provide / inject 替代 Vuex

了解了 provide / inject 的用法,下面来看怎样替代 Vuex。当然,我们的目的并不是为了替代 Vuex,它还是有相当大的用处,这里只是介绍另一种可行性。

使用 Vuex,最主要的目的是跨组件通信、全局数据维护、多人协同开发。需求比如有:用户的登录信息维护、通知信息维护等全局的状态和数据。

一般在 webpack 中使用 Vue.js,都会有一个入口文件 main.js,里面通常导入了 Vue、VueRouter、iView 等库,通常也会导入一个入口组件 app.vue 作为根组件。一个简单的 app.vue 可能只有以下代码:

<template>
  <div>
    <router-view></router-view>
  </div>
</template>
<script>
  export default {

  }
</script>

使用 provide / inject 替代 Vuex,就是在这个 app.vue 文件上做文章。

我们把 app.vue 理解为一个最外层的根组件,用来存储所有需要的全局数据和状态,甚至是计算属性(computed)、方法(methods)等。因为你的项目中所有的组件(包含路由),它的父组件(或根组件)都是 app.vue,所以我们把整个 app.vue 实例通过 provide 对外提供

app.vue:

<template>
  <div>
    <router-view></router-view>
  </div>
</template>
<script>
  export default {
    provide () {
      return {
        app: this
      }
    }
  }
</script>

上面,我们把整个 app.vue 的实例 this 对外提供,命名为 app(这个名字可以自定义,推荐使用 app,使用这个名字后,子组件不能再使用它作为局部属性)。

接下来,任何组件(或路由)只要通过 inject 注入 app.vue 的 app 的话,都可以直接通过 this.app.xxx 来访问 app.vue 的 data、computed、methods 等内容,而且因为注入的app是对象,所以也是 可响应 的。

app.vue 是整个项目第一个被渲染的组件,而且只会渲染一次(即使切换路由,app.vue 也不会被再次渲染),利用这个特性,很适合做一次性全局的状态数据管理,例如,我们将用户的登录信息保存起来:

app.vue,部分代码省略:

<script>
  export default {
    provide () {
      return {
        app: this
      }
    },
    data () {
      return {
        userInfo: null
      }
    },
    methods: {
      getUserInfo () {
        // 这里通过 ajax 获取用户信息后,赋值给 this.userInfo,以下为伪代码
        $.ajax('/user/info', (data) => {
          this.userInfo = data;
        });
      }
    },
    mounted () {
      this.getUserInfo();
    }
  }
</script>

这样,任何页面或组件,只要通过 inject 注入 app 后,就可以直接访问 userInfo 的数据了,比如:

<template>
  <div>
    {{ app.userInfo }}
  </div>
</template>
<script>
  export default {
    inject: ['app']
  }
</script>

是不是很简单呢。除了直接使用数据,还可以调用方法。比如在某个页面里,修改了个人资料,这时一开始在 app.vue 里获取的 userInfo 已经不是最新的了,需要重新获取。可以这样使用:

某个页面:

<template>
  <div>
    {{ app.userInfo }}
  </div>
</template>
<script>
  export default {
    inject: ['app'],
    methods: {
      changeUserInfo () {
        // 这里修改完用户数据后,通知 app.vue 更新,以下为伪代码
        $.ajax('/user/update', () => {
          // 直接通过 this.app 就可以调用 app.vue 里的方法
          this.app.getUserInfo();
        })
      }
    }
  }
</script>

同样非常简单。只要理解了 this.app 是直接获取整个 app.vue 的实例后,使用起来就得心应手了。想一想,配置复杂的 Vuex 的全部功能,现在是不是都可以通过 provide / inject 来实现了呢?

5. 进阶技巧

如果你的项目足够复杂,或需要多人协同开发时,在 app.vue 里会写非常多的代码,多到结构复杂难以维护。这时可以使用 Vue.js 的混合 mixins,将不同的逻辑分开到不同的 js 文件里。

比如上面的用户信息,就可以放到混合里:

user.js:

export default {
  data () {
    return {
      userInfo: null
    }
  },
  methods: {
    getUserInfo () {
      // 这里通过 ajax 获取用户信息后,赋值给 this.userInfo,以下为伪代码
      $.ajax('/user/info', (data) => {
        this.userInfo = data;
      });
    }
  },
  mounted () {
    this.getUserInfo();
  }
}

然后在 app.vue 中混合:

app.vue:

<script>
  import mixins_user from '../mixins/user.js';

  export default {
    mixins: [mixins_user],
    data () {
      return {

      }
    }
  }
</script>

这样,跟用户信息相关的逻辑,都可以在 user.js 里维护,或者由某个人来维护,app.vue 也就很容易维护了。

6. 结语

如果你顾忌 Vue.js 文档中所说,provide / inject 不推荐直接在应用程序中使用,那没有关系,仍然使用你熟悉的 Vuex 或 Bus 来管理你的项目就好。本文介绍的这对 API,主要还是在 独立组件 中发挥作用的。

只要一个组件使用了 provide 向下提供数据,那其下所有的子组件都可以通过 inject 来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据。需要注意的是,一旦注入了某个数据,比如上面示例中的 app,那这个组件中就不能再声明 app 这个数据了,因为它已经被父级占有。

所以,使不使用还是要看实际的业务需求,所有能满足业务相关的技术和技巧都可以择优选用。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值