摘要:引言本周精读的源码是这个库。这个库的目的是为了实现的依赖注入。精读那么开始源码的解析,首先是整体思路的分析。讨论地址是精读源码如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。前端精读帮你筛选靠谱的内容。
1. 引言
本周精读的源码是 inject-instance 这个库。
这个库的目的是为了实现 Class 的依赖注入。
比如我们通过 inject 描述一个成员变量,那么在运行时,这个成员变量的值就会被替换成对应 Class 的实例。这等于让 Class 具备了申明依赖注入的能力:
import {inject} from "inject-instance" import B from "./B" class A { @inject("B") private b: B public name = "aaa" say() { console.log("A inject B instance", this.b.name) } }
试想一下,如果成员函数 b 是通过 New 出来的:
class A { private b = new B() say() { console.log("A inject B instance", this.b.name) } }
这个 b 就不具备依赖注入的特点,因为被注入的 b 是外部已经初始化好的,而不是实例化 A 时动态生成的。
需要依赖注入的一般都是框架级代码,比如定义数据流,存在三个 Store 类,他们之间需要相互调用对方实例:
class A { @inject("B") private b: B } class B { @inject("C") private c: C } class C { @inject("A") private a: A }
那么对于引用了数据流 A、B、C 的三个组件,要保证它们访问到的是同一组实例 A B C 该怎么办呢?
这时候我们需要通过 injectInstance 函数统一实例化这些类,保证拿到的实例中,成员变量都是属于同一份实例:
import injectInstance from "inject-instance" const instances = injectInstance(A, B, C) instances.get("A") instances.get("B") instances.get("C")
那么框架底层可以通过调用 injectInstance 方式初始化一组 “正确注入依赖关系的实例”,拿 React 举例,这个动作可以发生在自定义数据流的 Provider 函数里:
那么在 Provider 函数内部通过 injectInstance 实例化的数据流,可以保证 A B C 操作的注入实例都是当前 Provider 实例中的那一份。
2. 精读那么开始源码的解析,首先是整体思路的分析。
我们需要准备两个 API: inject 与 injectInstance。
inject 用来描述要注入的类名,值是与 Class 名相同的字符串,injectInstance 是生成一系列实例的入口函数,需要生成最终生效的实例,并放在一个 Map 中。
injectinject 是个装饰器,它的目的有两个:
修改 Class 基类信息,使其实例化的实例能拿到对应字段注入的 Class 名称。
增加一个字段描述注入了那些 Key。
const inject = (injectName: string): any => (target: any, propertyKey: string, descriptor: PropertyDescriptor): any => { target[propertyKey] = injectName // 加入一个标注变量 if (!target["_injectDecorator__injectVariables"]) { target["_injectDecorator__injectVariables"] = [propertyKey] } else { target["_injectDecorator__injectVariables"].push(propertyKey) } return descriptor }
target[propertyKey] = injectName 这行代码中,propertyKey 是申明了注入的成员变量名称,比如 Class A 中,propertyKey 等于 b,而 injectName 表示这个值需要的对应实例的 Class 名,比如 Class A 中,injectName 等于 B。
而 _injectDecorator__injectVariables 是个数组,为 Class 描述了这个类参与注入的 key 共有哪些,这样可以在后面 injectInstance 函数中拿到并依次赋值。
injectInstance这个函数有两个目的:
生成对应的实例。
将实例中注入部分的成员变量替换成对应实例。
代码不长,直接贴出来:
const injectInstance = (...classes: Array) => { const classMap = new Map () const instanceMap = new Map () classes.forEach(eachClass => { if (classMap.has(eachClass.name)) { throw `duplicate className: ${eachClass.name}` } classMap.set(eachClass.name, eachClass) }) // 遍历所有用到的类 classMap.forEach((eachClass: any) => { // 实例化 instanceMap.set(eachClass.name, new eachClass()) }) // 遍历所有实例 instanceMap.forEach((eachInstance: any, key: string) => { // 遍历这个类的注入实例类名 if (eachInstance["_injectDecorator__injectVariables"]) { eachInstance["_injectDecorator__injectVariables"].forEach((injectVariableKey: string) => { const className = eachInstance.__proto__[injectVariableKey]; if (!instanceMap.get(className)) { throw Error(`injectName: ${className} not found!`); } // 把注入名改成实际注入对象 eachInstance[injectVariableKey] = instanceMap.get(className); }); } // 删除这个临时变量 delete eachInstance["_injectDecorator__injectVariables"]; }); return instanceMap }
可以看到,首先我们将传入的 Class 依次初始化:
// 遍历所有用到的类 classMap.forEach((eachClass: any) => { // 实例化 instanceMap.set(eachClass.name, new eachClass()) })
这是必须提前完成的,因为注入可能存在循环依赖,我们必须在解析注入之前就生成 Class 实例,此时需要注入的字段都是 undefined。
第二步就是将这些注入字段的 undefined 替换为刚才实例化 Map instanceMap 中对应的实例了。
我们通过 __proto__ 拿到 Class 基类在 inject 函数中埋下的 injectName,配合 _injectDecorator__injectVariables 拿到 key 后,直接遍历所有要替换的 key, 通过类名从 instanceMap 中提取即可。
__proto__ 仅限框架代码中使用,业务代码不要这么用,造成额外理解成本。
所以总结一下,就是提前实例化 + 根据 inject 埋好的信息依次替换注入的成员变量为刚才实例化好的实例。
3. 总结希望读完这篇文章,你能理解依赖注入的使用场景,使用方式,以及一种实现思路。
框架实现依赖注入都是提前收集所有类,统一初始化,通过注入函数打标后全局替换,这是一种思维套路。
如果有其他更有意思的依赖注入实现方案,欢迎讨论。
讨论地址是:精读《Inject Instance 源码》 · Issue #176 · dt-fe/weekly
如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
关注 前端精读微信公众号
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/105421.html
摘要:此处继承了上面的可以注入现成的状态管理实例,添加到之中。返回值写成的意义简单一句话概括,这么写可以避免改变导致子组件的重复渲染。就是创建状态管理组件时默认传递的监听函数,用的是的更新一个空对象。返回值写成的意义。 简介 unstated是一个极简的状态管理组件 看它的简介:State so simple, it goes without saying 对比 对比redux: 更加灵活...
摘要:返回,用来包裹顶层组件,向应用中注入状态管理实例,可做数据的初始化。方法返回创建的状态管理实例,作为参数传递给调用的函数,函数拿到实例,操作或显示数据。用来实现一个状态管理类。为中的状态管理实例数据。 个人网站: https://www.neroht.com 在React写应用的时候,难免遇到跨组件通信的问题。现在已经有很多的解决方案。 React本身的Context Redux结合...
阅读 3867·2021-07-28 18:10
阅读 2576·2019-08-30 15:44
阅读 1081·2019-08-30 14:07
阅读 3454·2019-08-29 17:20
阅读 1577·2019-08-26 18:35
阅读 3533·2019-08-26 13:42
阅读 1815·2019-08-26 11:58
阅读 1585·2019-08-23 18:33