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

141 天前
 zhennann

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

3. 注册名

通过注册名在 ioc 容器中查找并注入 Bean 实例,如果不存在则返回空值。这种机制一般用于同模块注入层级注入

import type { ModelTodo } from '../../bean/model.todo.js';

class ControllerTodo {
  @Use({ name: '$$modelTodo' })
  $$modelTodo: ModelTodo;
}

4. 属性名

通过属性名在 ioc 容器中查找并注入 Bean 实例,如果不存在则返回空值。这种机制一般用于同模块注入层级注入

import type { ModelTodo } from '../../bean/model.todo.js';

class ControllerTodo {
  @Use()
  $$modelTodo: ModelTodo;
}

注入范围

匿名 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;
}

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;
}

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;
}

层级注入

注入范围除了支持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;
}

5. skipSelf

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

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

2511 次点击
所在节点    Node.js
29 条回复
qrobot
139 天前
与以下的本质上有什么区别?

```
class Demo2 {
constructor(b2) {
this.b2 = b2
}
}
```

相对于 IoC, 这几点非常蛋疼


1. 会导致 tree shaking 完全失效
2. 多一个 runtime 开销
3. 增加调试的复杂度
unco020511
139 天前
前端需要 ioc 的理由是什么?
gogozs
139 天前
JAVA ptsd 了
zhennann
138 天前
@qrobot ts 与 java 装饰器的不同:ts 装饰器不仅仅是装饰,而且可以在代码初始化时,执行一段初始化逻辑,从而主动在系统中注册资源。而 java 装饰器没有这个主动初始化的阶段,因此需要扫描

1. Zova 提供了模块化体系,以模块为单位实现独立的打包,从而也是以模块为单位实现异步加载。这确实存在 tree shaking 失效的问题,但是可以避免打包产物碎片化严重的问题,同时也能避免初始包过大的问题。对于小项目,tree shaking 可能优先于碎片化,对于中大项目,碎片化和初始包大小可能优先于 tree shaking 。这是一个 trade-off 问题
2. 多一个 runtime 开销是否值得,也和项目的规模有关
3. 调试是否复杂跟代码结构有关。Zova 提供了更多的代码规范,代码更加清晰,或许更容易调试一些。反之,原始的 Vue3 并没有对业务架构做出更多的约定,也没有提供现成的最佳实践,代码风格反而难以统一。
zhennann
138 天前
@unco020511 请参见这篇文档:为什么需要 Vue3+IOC: https://zova.js.org/zh/guide/start/why.html
zhennann
138 天前
@gogozs Zova 与 Java 的代码风格有显著的不同,体现在以下两个方面:
1. 更少的装饰器函数:Zova 采用依赖注入与依赖查找相结合的策略,优先使用依赖查找,从而大量减少装饰器函数的使用
2. 更少的类型标注:Zova 优先使用依赖查找可以达到化类型于无形的开发体验,也就是不需要标注类型就可以享受到类型编程的诸多好处,从而让我们的代码始终保持简洁和优雅,进而显著提升开发效率,保证代码质量
qrobot
106 天前
@zhennann 你说了这么多我实在是看不到任何优点, 无非是把 Spring 这一套强行拿到 前端来, 这非常过度设计.

因为项目足够大, 一个 runtime 的开销非常恐怖, 你自己看看, 现在主流的都是想着怎么去 runtime , 反而你还在里面加 runtime 反其道而行之


webpack 之初就是为了 tree shaking 和 code splitting, 现在你把 tree shaking 的功能完全丢弃了.

其次你的想法很好, 站在项目工程角度上来考虑这个事情, 这些东西我觉得这完全是将简单的东西复杂化.
qrobot
106 天前
@zhennann 两年之后你可以在回过头来看我说的这句话, 越简单的代码往往越可靠
zhennann
72 天前
@qrobot 小型项目与大型项目的诉求不同,对框架设计的要求也就不同。对于大型项目而言,通过一个精炼的 runtime 把常用的开发范式内聚成一个核心,不仅有利于规范团队开发,也可以大量减少重复性代码,让精力更加聚焦于业务领域本身。我这里可以举两个例子:
第一个例子:在实际开发当中,会遇到三个场景的状态共享:组件内部状态共享、组件之间状态共享、全局状态共享。在传统的 Vue3 当中,分别采用不同的机制来实现,而在 Zova 中只需要采用统一的 IOC 容器机制即可。参见:[简洁而强大的 IOC 容器]( https://zova.js.org/zh/guide/essentials/ioc/introduction.html)
第二个例子:在实际开发当中,会遇到四种全局状态数据:异步数据(一般来自服务端)、同步数据。同步数据又分为三种:localstorage 、cookie 、内存。在传统的 Vue3 当中,分别采用不同的机制来处理这些状态数据,那么可否采用统一的机制进行管理呢?此外,对于大型项目,用户需要长时间进行界面交互的场景,如果存在过多的全局状态数据,就会导致内存占用过多,有什么破解之道呢? Zova 提供的 Model 机制可以用更优雅、更简洁的代码解决以上问题,参见:[Model: 统一数据源]( https://zova.js.org/zh/guide/techniques/model/introduction.html)
此外,经过近半年的进化,Zova 的整体架构得到进一步精简,并且提供了 VSCode 插件,通过右键菜单提供大量工具,显著提升开发体验,包括四大类能力:Create/Init/Refactor/Tools 。若有空可以一试。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1061534

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX