本週精讀的源碼是 inject-instance 這個庫。前端
這個庫的目的是爲了實現 Class 的依賴注入。git
好比咱們經過 inject
描述一個成員變量,那麼在運行時,這個成員變量的值就會被替換成對應 Class 的實例。這等於讓 Class 具有了申明依賴注入的能力:github
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
該怎麼辦呢?ide
這時候咱們須要經過 injectInstance
函數統一實例化這些類,保證拿到的實例中,成員變量都是屬於同一份實例:函數
import injectInstance from 'inject-instance'
const instances = injectInstance(A, B, C)
instances.get('A')
instances.get('B')
instances.get('C')
複製代碼
那麼框架底層能夠經過調用 injectInstance
方式初始化一組 「正確注入依賴關係的實例」,拿 React 舉例,這個動做能夠發生在自定義數據流的 Provider
函數裏:ui
<Provider stores={{ A, B, C }}>
<Root /> </Provider>
複製代碼
那麼在 Provider
函數內部經過 injectInstance
實例化的數據流,能夠保證 A
B
C
操做的注入實例都是當前 Provider
實例中的那一份。this
那麼開始源碼的解析,首先是總體思路的分析。
咱們須要準備兩個 API: inject
與 injectInstance
。
inject
用來描述要注入的類名,值是與 Class 名相同的字符串,injectInstance
是生成一系列實例的入口函數,須要生成最終生效的實例,並放在一個 Map 中。
inject
是個裝飾器,它的目的有兩個:
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
函數中拿到並依次賦值。
這個函數有兩個目的:
代碼不長,直接貼出來:
const injectInstance = (...classes: Array<any>) => {
const classMap = new Map<string, any>()
const instanceMap = new Map<string, any>()
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.0 許可證)