摘要:引言本周精讀的源碼是這個庫。這個庫的目的是為了實現的依賴注入。精讀那么開始源碼的解析,首先是整體思路的分析。討論地址是精讀源碼如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。前端精讀幫你篩選靠譜的內容。
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
如果你想參與討論,請 點擊這里,每周都有新的主題,周末或周一發布。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公眾號
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105421.html
摘要:此處繼承了上面的可以注入現成的狀態管理實例,添加到之中。返回值寫成的意義簡單一句話概括,這么寫可以避免改變導致子組件的重復渲染。就是創建狀態管理組件時默認傳遞的監聽函數,用的是的更新一個空對象。返回值寫成的意義。 簡介 unstated是一個極簡的狀態管理組件 看它的簡介:State so simple, it goes without saying 對比 對比redux: 更加靈活...
摘要:返回,用來包裹頂層組件,向應用中注入狀態管理實例,可做數據的初始化。方法返回創建的狀態管理實例,作為參數傳遞給調用的函數,函數拿到實例,操作或顯示數據。用來實現一個狀態管理類。為中的狀態管理實例數據。 個人網站: https://www.neroht.com 在React寫應用的時候,難免遇到跨組件通信的問題?,F在已經有很多的解決方案。 React本身的Context Redux結合...
閱讀 3871·2021-07-28 18:10
閱讀 2580·2019-08-30 15:44
閱讀 1087·2019-08-30 14:07
閱讀 3464·2019-08-29 17:20
閱讀 1579·2019-08-26 18:35
閱讀 3538·2019-08-26 13:42
閱讀 1820·2019-08-26 11:58
閱讀 1592·2019-08-23 18:33