provide/inject深度学习看这篇文章帮你记住使用provide/inject传递响应式数据时的一个特性:provide传递的每一个响应式数据都需要是一个值引用不可变的Listenable对象。在开发Vue项目时,不可避免地要进行组件之间的通信。如果是在实际的业务项目中,组件之间的通信可以使用vuex、EventBus等机制来实现跨组件通信。但是如果在开发基础组件库的时候需要和业务项目的外部环境(vuex、EventBus)解耦,就不能使用这些机制了。一般的解决方案是模仿事件冒泡和广播实现基础库中组合组件的通信,比如element-ui的emitter。vue2.2.0版本新增provide/injectapi,通过引用实现react的context功能。它还为跨组件通信提供了一种新的方式。从官网文档来看,这是一个比emitter优雅很多的机制,但是有一个特殊的提示。提示:提供和注入绑定没有响应。这是故意的。但是,如果你传入一个listenable对象,它的属性仍然是响应式的。也就是说provide/inject机制对responsivedelivery有很大的限制,那么它能不能完全替代emitter机制呢?但是在实际开发中,父子组件是组合在一起使用的。一般在实现的时候不会使用事件冒泡或者事件广播来进行通信。一般通过递归或者循环获取$parent来获取祖先组件的数据,因为这些数据是响应式的,不需要像事件传递那样繁琐。如果子组件中有代码:computed:{count(){return(this.$parent||{}).count;},user(){return(this.$parent||{}).user;},},当父组件的count(基本类型)或user(引用类型)发生变化时,子组件的computed会自动更新。这在高级组件中非常有用。您可以将子组件的一些共同特征提取到父组件中。父组件为这些子组件的一些属性提供默认值。更改父组件的props以更改所有默认值。子组件的行为。例如el-form中的label-width可以为所有的目标子组件el-form-item设置一个默认的label-width。在实际使用中,并不会直接从this.$parent中获取目标组件,因为耦合度太高。一般是通过循环或递归的方式来查找具有某些特征的组件。具体可以参考如何在element-ui的emitter中搜索目标组件触发事件。$children不支持响应式。接下来在组件树中使用provide从上到下传递数据,通过provide在父组件中暴露一些数据,子组件接受这些数据,然后通过子组件渲染到页面上。在父组件中更改这些值,观察数据流向。如果在父组件中提供:props:{count:Number,user:Object,},data(){return{grade:{number:12,},};},provide(){return{count:this.count,user:this.user,grade:this.grade,};},其中user是外部组件通过props传递给父组件的数据,结构为:{name:''.age:0,address:{number:0,}}这些数据user和grade都是响应式数据结构,而count不是。分别更改这些数据结构,依次更改grade.number、user.address.number、user.age、user.address,会发现子组件可以接收响应式数据,但是更改grade、user、count时,subcomponent组件无法响应式渲染页面,改变grade和user属性也会导致非响应式渲染。其中,由于count是一个不可观察的对象,所以不会在子组件中动态渲染,符合预期。之后,当改变grade.number、user.address.number、user.age、user.address时,子组件可以动态渲染和改变值,因为这些属性都在一个listenable对象中。改完grade和user后,发现子组件没有动态渲染,证明grade和user没有响应,因为grade和user不在一个listenable对象中。等级在哪里,用户?在provide语句中,返回了一个对象:provide(){return{count:this.count,user:this.user,grade:this.grade,};}grade,user在这个对象中,对象不是Listenable对象,所以grade和user是没有响应的。第一种解决方案是将此对象作为响应式对象,在data中声明一个容器字段来包装需要传递的数据,如:data(){return{context:{user:'',count:0,等级:'',},等级:{number:12,user},};},watch:{.con.user=thisval(val;},count(val){this.context.count=val;},grade(val){this.context.grade=val;},},创建(){this.context.user=this.user;this.context.count=this.count;this.context.grade=this.grade;},为什么这样写,因为provide/inject机制不支持responsive编程,后续直接修改provide返回的对象不会重新刷新provide/inject机制,即provide返回对象的顶层响应机制失效,对象的顶层属性无法操作,该机制会导致以下三种方法无法实现响应式投放:上面的上下文不能在computed中声明。因为computed每次都会返回一个新的值(reference),而provide只会记录初始context的reference,后续的数据变化,不会刷新新的context来provide。上面的context是在data中声明的,但是如果在某处执行了this.context={...},provide中不会更新新的context,provide中的context会一直复制到Hisquote中。这样会导致父组件中的context动态刷新,而子组件中的context不会动态刷新。在provide函数中直接返回上面的context,那么user和grade就会成为顶级属性,created中的reassignment操作以及后续的reassignment操作都不会响应provide,失去响应能力。根据上面的写法,发现可以在子组件中动态渲染grade和user。由上可知,如果provide传递的数据一直是响应式的,那么provide传递的每个属性的值都需要是引用不变的可监听对象。每次都维护上下文太麻烦了。有简单的方法吗?可以这样写:provide(){return{group:this,};}直接把父组件的引用放在provide返回的对象的一个??属性中,this代表当前实例,引用不会改变,而且this是大的大多数属性都是reactive的。但是需要注意的是,大多数以$为前缀的属性都不是响应式属性,比如$el,当子组件使用这些属性时,是不会动态渲染的。如果父组件作用域比较大,比如同时服务多种类型的子组件,或者允许使用第三方子组件,建议不要直接传这个,而是暴露一个具体的api在提供。但是按照上面维护一个具体的context对象太麻烦了,可以用一个函数保证引用不变(推荐这种方式):provide(){return{getContext:()=>({user:this.user,等级:this.grade,})};}在子组件中:inject:['getContext'],computed:{context(){returnthis.getContext();}}一般来说,provide/inject是可以完全替代emitter机制的,包括事件冒泡和事件广播。并且provide/inject也为父组件向子组件暴露API提供了一种更安全的方式。通过阅读vue中provide/inject的源码),发现provide/inject的机制也是通过$parent实现的。Provide将在初始化期间放置在_provided属性中。当子组件访问inject的值时,会通过$parent属性查找所有祖先节点的_provided,获取第一个target属性的值。为什么provide返回的对象的最顶层响应机制会失效?在查找inject的值时,会取出target_provide的单个属性)放在一个无响应的对象中。从设计的角度来看也是合理的。Provide是数据集,不是数据集。子组件注入的数据可以从多个提供者中选择。因此,父组件的提供不需要维护这个数据集的响应性。通过阅读inject的源码我们可以发现,其实获取inject的值也是通过$parent查找祖先中的值,直接获取目标组件的_provided属性的值。子组件和父组件共享一个变量,在子组件中改变这个值,父组件中的值也会改变。
