為什麼要為Vue3提供ioc容器
Vue3因其出色的響應式系統,以及便利的功能特性,完全勝任大型業務系統的開發。但是,我們不僅要能做到,而且要做得更好。大型業務系統的關鍵就是解耦合,從而減緩shi山程式碼的生長。而ioc容器是目前最好的解耦合工具。Angular從一開始就引入了ioc容器,因此在業務工程化方面一直處於領先地位,並且一直在向其他前端框架招手:“我在前面等你們,希望三年後能再見”。那麼,我就試著向前走兩步,在Vue3中引入ioc容器,並以此為基礎擴充其他工程能力,得到一個新框架:Zova。諸君覺得是否好用,歡迎拍磚、交流:
IOC容器分類
在 Zova 中有兩類 ioc 容器:
全域性ioc容器
:在系統初始化時,會自動建立唯一一個全域性 ioc 容器。在這個容器中建立的Bean例項都是單例模式元件例項ioc容器
:在建立 Vue 元件例項時,系統會為每一個 Vue 元件例項建立一個 ioc 容器。在這個容器中建立的Bean例項可以在元件例項範圍之內共享資料和邏輯
Bean Class分類
Zova 採用模組化體系,Bean Class 都由不同的模組提供。使用模組內部的 Bean Class 時可以直接基於Class型別
定位。在跨模組使用時可以基於Bean標識
定位,而不是基於Class型別/檔案路徑
定位,這樣有利於實現模組之間的松耦合
因此,Zova 提供了兩類 Bean Class:
匿名bean
:使用@Local
裝飾的 class 就是匿名bean
。此類 bean 僅在模組內部使用,不存在命名衝突的問題,定義和使用都很便捷具名bean
:除了@Local
之外,其他裝飾器函式裝飾的 class 都是具名bean
。Zova 為此類 bean 提供了命名規範,既可以避免命名衝突,也有利於跨模組使用
注入機制
Zova 透過@Use
裝飾器函式注入 Bean 例項,提供了以下幾種注入機制:
1. Bean Class
透過Bean Class
在 ioc 容器中查詢並注入 Bean 例項,如果不存在則自動建立。這種機制一般用於同模組注入
import { ModelTodo } from '../../bean/model.todo.js';
class ControllerTodo {
@Use()
$$modelTodo: ModelTodo;
}
2. Bean標識
透過Bean標識
在 ioc 容器中查詢並注入 Bean 例項,如果不存在則自動建立。這種機制一般用於跨模組注入
和層級注入
import type { ModelTabs } from 'zova-module-a-tabs';
class ControllerLayout {
@Use('a-tabs.model.tabs')
$$modelTabs: ModelTabs;
}
- 透過
a-tabs.model.tabs
查詢並注入 Bean 例項 - 因此,只需匯入 ModelTabs 的 type 型別,從而保持模組之間的松耦合關係
3. 註冊名
透過註冊名
在 ioc 容器中查詢並注入 Bean 例項,如果不存在則返回空值。這種機制一般用於同模組注入
和層級注入
import type { ModelTodo } from '../../bean/model.todo.js';
class ControllerTodo {
@Use({ name: '$$modelTodo' })
$$modelTodo: ModelTodo;
}
- 透過註冊名
$$modelTodo
查詢並注入 Bean 例項。一般而言,應該確保在 ioc 容器中已經事先注入過 Bean 例項,否則就會返回空值
4. 變數名
透過變數名
在 ioc 容器中查詢並注入 Bean 例項,如果不存在則返回空值。這種機制一般用於同模組注入
和層級注入
import type { ModelTodo } from '../../bean/model.todo.js';
class ControllerTodo {
@Use()
$$modelTodo: ModelTodo;
}
- 透過變數名
$$modelTodo
查詢並注入 Bean 例項。一般而言,應該確保在 ioc 容器中已經事先注入過 Bean 例項,否則就會返回空值
注入範圍
匿名bean
的預設注入範圍都是ctx
,具名bean
可以在定義時指定預設注入範圍,不同的場景(scene)有不同的預設注入範圍。 此外,在實際注入時,還可以在@Use 中透過injectionScope
選項覆蓋預設的注入範圍
Zova 提供了以下幾種注入範圍:app/ctx/new/host/skipSelf
1. app
如果注入範圍是 app,那麼就在全域性 ioc 容器中注入 bean 例項,從而實現單例的效果
// in module: test-module1
@Store()
class StoreCounter {}
// in module: test-module2
import type { StoreCounter } from 'zova-module-test-module1';
class Test {
@Use('test-module1.store.counter')
$$storeCounter: StoreCounter;
}
- Store 的注入範圍預設是 app,因此透過 Bean 標識
test-module1.store.counter
在全域性 ioc 容器中查詢並注入 bean 例項
2. ctx
如果注入範圍是 ctx,那麼就在當前元件例項的 ioc 容器中注入 bean 例項
// in module: a-tabs
@Model()
class ModelTabs {}
// in module: test-module2
import type { ModelTabs } from 'zova-module-a-tabs';
class ControllerLayout {
@Use('a-tabs.model.tabs')
$$modelTabs: ModelTabs;
}
- Model 的注入範圍預設是 ctx,因此透過 Bean 標識
a-tabs.model.tabs
在當前元件例項的 ioc 容器中查詢並注入 bean 例項
3. new
如果注入範圍是 new,那麼就直接建立新的 bean 例項
// in module: a-tabs
@Model()
class ModelTabs {}
// in module: test-module2
import type { ModelTabs } from 'zova-module-a-tabs';
class ControllerLayout {
@Use({ beanFullName: 'a-tabs.model.tabs', injectionScope: 'new' })
$$modelTabs: ModelTabs;
}
- 由於指定 injectionScope 選項為 new,因此透過 Bean 標識
a-tabs.model.tabs
直接建立新的 bean 例項
層級注入
注入範圍除了支援app/ctx/new
,還支援層級注入:host/skipSelf
4. host
如果注入範圍是 host,那麼就在當前元件例項的 ioc 容器以及所有父容器中依次查詢並注入 bean 例項,如果不存在則返回空值
// in parent component
import type { ModelTabs } from 'zova-module-a-tabs';
class Parent {
@Use('a-tabs.model.tabs')
$$modelTabs: ModelTabs;
}
// in child component
import type { ModelTabs } from 'zova-module-a-tabs';
class Child {
@Use({ injectionScope: 'host' })
$$modelTabs: ModelTabs;
}
- 由於父元件已經注入了 ModelTabs 的 bean 例項,因此子元件可以直接查詢並注入
層級注入
同樣支援所有注入機制:Bean Class/Bean標識/註冊名/變數名
5. skipSelf
如果注入範圍是 skipSelf,那麼就在所有父容器中依次查詢並注入 bean 例項,如果不存在則返回空值
Zova已開源:https://github.com/cabloy/zova