加油,為Vue3提供一個可媲美Angular的ioc容器

濮水大叔發表於2024-07-31

為什麼要為Vue3提供ioc容器

Vue3因其出色的響應式系統,以及便利的功能特性,完全勝任大型業務系統的開發。但是,我們不僅要能做到,而且要做得更好。大型業務系統的關鍵就是解耦合,從而減緩shi山程式碼的生長。而ioc容器是目前最好的解耦合工具。Angular從一開始就引入了ioc容器,因此在業務工程化方面一直處於領先地位,並且一直在向其他前端框架招手:“我在前面等你們,希望三年後能再見”。那麼,我就試著向前走兩步,在Vue3中引入ioc容器,並以此為基礎擴充其他工程能力,得到一個新框架:Zova。諸君覺得是否好用,歡迎拍磚、交流:

IOC容器分類

在 Zova 中有兩類 ioc 容器:

  1. 全域性ioc容器:在系統初始化時,會自動建立唯一一個全域性 ioc 容器。在這個容器中建立的Bean例項都是單例模式
  2. 元件例項ioc容器:在建立 Vue 元件例項時,系統會為每一個 Vue 元件例項建立一個 ioc 容器。在這個容器中建立的Bean例項可以在元件例項範圍之內共享資料和邏輯

Bean Class分類

Zova 採用模組化體系,Bean Class 都由不同的模組提供。使用模組內部的 Bean Class 時可以直接基於Class型別定位。在跨模組使用時可以基於Bean標識定位,而不是基於Class型別/檔案路徑定位,這樣有利於實現模組之間的松耦合

因此,Zova 提供了兩類 Bean Class:

  1. 匿名bean:使用@Local裝飾的 class 就是匿名bean。此類 bean 僅在模組內部使用,不存在命名衝突的問題,定義和使用都很便捷
  2. 具名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

相關文章