加油,为 Vue3 提供一个可媲美 Angular 的 ioc 容器

13次阅读

共计 3136 个字符,预计需要花费 8 分钟才能阅读完成。

为什么要为 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:

  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 中通过 containerScope 选项覆盖默认的注入范围

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', containerScope: 'new'})
  $$modelTabs: ModelTabs;
}
  • 由于指定 containerScope 选项为 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({ containerScope: 'host'})
  $$modelTabs: ModelTabs;
}
  • 由于父组件已经注入了 ModelTabs 的 bean 实例,因此子组件可以直接查找并注入
  • 层级注入 同样支持所有注入机制:Bean Class/Bean 标识 / 注册名 / 属性名

5. skipSelf

如果注入范围是 skipSelf,那么就在所有父容器中依次查找并注入 bean 实例,如果不存在则返回空值

Zova 已开源:https://github.com/cabloy/zova

正文完
 0