当前位置: 首页 > Web前端 > HTML

注入不起作用?!依赖注入背后的实现原理和运行逻辑是怎样的?

时间:2023-03-29 11:36:28 HTML

一个问题如上图所示。我们先想一个问题。宿主项目使用业务组件库中的组件,然后在宿主项目中的业务组件中注入一个名为date的key,其值为当前的Timestamp,那么业务组件能否获取到宿主项目注入的数据呢?在回答这个问题之前,我们先看看provide和inject是怎么使用的。依赖注入provide为组件后代提供数据,需要使用provide()函数:hello!')如果你不使用最后,如果如果你想确保从provide传递的数据不能被注入器组件更改,你可以使用readonly()来包装提供的值。使用Symbol作为注入名称到目前为止,我们已经看到了如何使用字符串作为注入名称。但是如果你正在构建一个依赖很多的大型应用程序,或者你正在为其他开发人员编写组件库,建议使用Symbol作为注入名称以避免潜在的冲突。建议将这些注入名称Symbol导出到一个单独的文件中:datatobesupplied*/});//注入器组件import{inject}from'vue'import{myInjectionKey}from'./keys.js'constinjected=inject(myInjectionKey)实现原理依赖于我们有了一个之后对注入的大致了解,让我们来看看它是如何工作的。直接上源代码:exportfunctionprovide(key:InjectionKey|string|number,value:T){if(!currentInstance){if(__DEV__){warn(`provide()canonlybeusedinsidesetup().`)}}else{letprovides=currentInstance.providesconstparentProvides=currentInstance.parent&¤tInstance.parent.providesif(parentProvides===provides){provides=currentInstance.provides=Object.create(parentProvides)}//TS不允许符号作为索引类型提供[keyasstring]=value}}exportfunctioninject(key:InjectionKey|string):T|undefinedexport函数inject(key:InjectionKey|string,defaultValue:T,treatDefaultAsFactory?:false):Texport函数inject(key:InjectionKey|string,defaultValue:T|(()=>T),treatDefaultAsFactory:true):Texport函数inject(key:InjectionKey|string,defaultValue?:unknown,treatDefaultAsFactory=false){//回退到`currentRenderingInstance`这样就可以在//功能组件中调用constinstance=currentInstance||currentRenderingInstanceif(instance){//#2400//支持`app.use`插件,//如果实例位于根目录,则回退到appContext的`provides`constprovides=instance.parent==null?instance.vnode.appContext&&instance.vnode.appContext.provides:instance.parent.providesif(provides&&(keyasstring|symbol)inprovides){//TS不允许symbol作为索引类型returnprovides[keyasstring]}elseif(arguments.length>1){returntreatDefaultAsFactory&&isFunction(defaultValue)?defaultValue.call(instance.proxy):defaultValue}elseif(__DEV__){warn(`injection"${String(key)}"notfound.`)}}elseif(__DEV__){warn(`inject()只能在setup()或函数式组件内部使用`)}}源码位置:packages/runtime-core/src/apiInject.ts抛开一开始提出的问题,我们来看一下provide的源码,注意如下代码:是重复的,对于子组件,需要取最近的父级组件的值。这里的解决方案是使用原型链来解决provides初始化。它在创建createComponent时被处理。那时,parent.provides直接赋值给组件的provides。因此,如果发现provides和parentProvides相等,说明是第一次provide(针对当前组件),我们可以重新赋值parent.provides作为currentInstance.provides的原型。至于为什么不在createComponent的时候做这个处理,可能的好处是如果在这里初始化的话,会有懒执行的效果(优化点,只在需要的时候初始化)。看完了provide的源码,我们再来看看inject的源码。inject的执行逻辑比较简单。首先,获取当前实例。如果当前实例存在,则进一步判断当前实例的父实例是否存在。如果父实例存在,则取父实例的provides进行注入。如果父实例不存在,则取全局实例。(appContext)提供注入。注入失败?看完了provide和inject的源码,我们来分析一下文章开头提出的问题。我们将来自宿主项目提供者的密钥注入到业务组件中。业务组件会先找到当前组件(实例),然后根据当前组件根据父组件的provides注入。显然,我们可以在业务组件中获取注入到宿主项目中的数据。第二个问题分析完文章开头提出的问题,我们来看另一个有趣的问题。下图中的业务组件能否获取宿主项目注入的数据?答案可能和你想的有点出入:这个时候我们就拿不到宿主项目注入的数据了!!!问题是什么?问题出在Symbol上。其实在这个场景下,宿主项目引入的Symbol和业务组件库引入的Symbol,本质上并不是同一个Symbol,因为在不同的应用中创建的Symbol实例总是唯一的。如果我们想让所有的应用程序共享一个Symbol实例,这时候就需要另外一个API来创建或获取Symbol,即Symbol.for(),它可以注册或获取一个window全局Symbol实例。我们的公共二方库(common)只需要修改如下:exportconstdate=Symbol.for('date');综上所述,如果我们要注入上层提供的provide,需要注意以下几点:保证inject和provide的组件在同一个组件树中,并且使用Symbol作为键值,请确保两个组件在同一个应用中如果两个组件不在同一个应用中,请使用Symbol.for创建一个全局Symbol实例作为key值参考更多精彩,请关注我们的公众号「百瓶科技」,不定期有福利!