Vue3 因其出色的响应式系统,以及便利的功能特性,完全胜任大型业务系统的开发。但是,我们不仅要能做到,而且要做得更好。大型业务系统的关键就是解耦合,从而减缓 shi 山代码的生长。而 ioc 容器是目前最好的解耦合工具。Angular 从一开始就引入了 ioc 容器,因此在业务工程化方面一直处于领先地位,并且一直在向其他前端框架招手:“我在前面等你们,希望三年后能再见”。那么,我就试着向前走两步,在 Vue3 中引入 ioc 容器,并以此为基础扩充其他工程能力,得到一个新框架:Zova。诸君觉得是否好用,欢迎拍砖、交流:
在 Zova 中有两类 ioc 容器:
全局 ioc 容器
:在系统初始化时,会自动创建唯一一个全局 ioc 容器。在这个容器中创建的 Bean 实例都是单例模式组件实例 ioc 容器
:在创建 Vue 组件实例时,系统会为每一个 Vue 组件实例创建一个 ioc 容器。在这个容器中创建的 Bean 实例可以在组件实例范围之内共享数据和逻辑在 Zova 中有两类 Bean Class:
匿名 bean
:使用@Local
装饰的 class 就是匿名 bean
。此类 bean 仅在模块内部使用,不存在命名冲突的问题,定义和使用都很便捷具名 bean
:除了@Local
之外,其他装饰器函数装饰的 class 都是具名 bean
。Zova 为此类 bean 提供了命名规范,既可以避免命名冲突,也有利于跨模块使用Zova 通过@Use
装饰器函数注入 Bean 实例,提供了以下几种注入机制:
通过Bean Class
在 ioc 容器中查找并注入 Bean 实例,如果不存在则自动创建。这种机制一般用于同模块注入
import { ModelTodo } from '../../bean/model.todo.js';
class ControllerTodo {
@Use()
$$modelTodo: ModelTodo;
}
通过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 实例通过注册名
在 ioc 容器中查找并注入 Bean 实例,如果不存在则返回空值。这种机制一般用于同模块注入
和层级注入
import type { ModelTodo } from '../../bean/model.todo.js';
class ControllerTodo {
@Use({ name: '$$modelTodo' })
$$modelTodo: ModelTodo;
}
$$modelTodo
查找并注入 Bean 实例。一般而言,应该确保在 ioc 容器中已经事先注入过 Bean 实例,否则就会返回空值通过属性名
在 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
如果注入范围是 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;
}
test-module1.store.counter
在全局 ioc 容器中查找并注入 bean 实例如果注入范围是 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;
}
a-tabs.model.tabs
在当前组件实例的 ioc 容器中查找并注入 bean 实例如果注入范围是 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;
}
a-tabs.model.tabs
直接创建新的 bean 实例注入范围除了支持app/ctx/new
,还支持层级注入:host/skipSelf
如果注入范围是 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;
}
层级注入
同样支持所有注入机制:Bean Class/Bean 标识/注册名/属性名
如果注入范围是 skipSelf ,那么就在所有父容器中依次查找并注入 bean 实例,如果不存在则返回空值
Zova 已开源: https://github.com/cabloy/zova
1
lisongeee 108 天前 1
|
3
LuckyLauncher 108 天前
Vue/React 社区为什么不引入 IoC 容器是有原因的
在前端框架领域没有 IoC 的占有率反而吊打有 IoC 的,前端真的需要 IoC 吗 |
4
wuyiccc 108 天前
node 后台用 ioc 可以理解,前端页面为什么要用 ioc 呢,会想 springboot 一样随便打开一个页面就会扫描所有的 class 么?
|
5
shimada666 108 天前
@lisongeee +1
|
9
zhennann OP @qrobot 前端是异步体系,许多模块都是按需异步加载的,采用 componentScan 不能解决所有问题。在 Zova 中,装饰过的 class 在初始化时就自动注册到系统中了,不需要扫描
|
10
calmbinweijin 107 天前
@lisongeee 感谢,这边看见了《我们团队是如何用好 vue3 setup 组合式 API 的?》感觉很实用,之前一直以 vue3 的组合式 API 开发 vue2 的选项式代码。感觉才有点理解了组合式 API
|
11
ZGame 107 天前
@zhennann 对于 vue 来说 ioc 没有意义, 我可以使用他自身或者第三方的 store,他还可以构造 dispatch action 等概念。等于原生 js 或者 ts 来说 那第三方的 ioc 可选择余地就更多了。 而如果你希望引入 class 概念对于业务逻辑进行封装... emmm, 其实也没错, 但是 vue 目前趋势是 hooks+对应 api 去解耦 和限制数据。 所以你的 ioc 来说,是某种程度上个可以代替相关逻辑 但是契合度太低了
|
12
ZGame 107 天前
scope 概念很好 但是感觉 class 契合度太低了。
|
14
ZGame 106 天前
@zhennann 因为 hooks 某种程度上就是通过函数式的方式代替 class 的业务逻辑的。而且因为有约束处理的好的话 入参出参 副作用这些一定程度上能代替 class,而且如果我不用到 scope 的话 为啥我不直接用第三方的 store 来存储数据 这样数据流单向而且更清晰, 所以我觉得除非有一些案例,能很简单的说非这样注入不可的,那我觉得很难引入到里面。 如果纯 js, ts 底层库的话,而不关联 vue,react 这类框架使用的话 ,应该有一定价值
|
15
KuroNekoFan 106 天前
太可怕了,java 人
|
16
juzisang 106 天前 1
|
17
juzisang 106 天前
vue 这些框架,感觉只是提供了一个视图层面的规范和约束,但是在业务逻辑方面,没啥最佳实践。
hook 和 class 在代码在组织业务逻辑方面,感觉没啥区别,只是写法不同而已。 反倒是 class 实际更契合 js 的语法特性,写起来也更方便。 在维护一个长期项目上,由于前端框架更迭太快了,时间跨度可能有好几年。 这个时候,耦合太多这些视图框架的特性在业务代码里,后续想更新甚至更换视图框架,都会很麻烦。 |
19
novaline 106 天前
强行 IoC
|
20
qrobot 106 天前
|
21
qrobot 106 天前
与以下的本质上有什么区别?
``` class Demo2 { constructor(b2) { this.b2 = b2 } } ``` 相对于 IoC, 这几点非常蛋疼 1. 会导致 tree shaking 完全失效 2. 多一个 runtime 开销 3. 增加调试的复杂度 |
22
unco020511 106 天前
前端需要 ioc 的理由是什么?
|
23
gogozs 106 天前
JAVA ptsd 了
|
24
zhennann OP @qrobot ts 与 java 装饰器的不同:ts 装饰器不仅仅是装饰,而且可以在代码初始化时,执行一段初始化逻辑,从而主动在系统中注册资源。而 java 装饰器没有这个主动初始化的阶段,因此需要扫描
1. Zova 提供了模块化体系,以模块为单位实现独立的打包,从而也是以模块为单位实现异步加载。这确实存在 tree shaking 失效的问题,但是可以避免打包产物碎片化严重的问题,同时也能避免初始包过大的问题。对于小项目,tree shaking 可能优先于碎片化,对于中大项目,碎片化和初始包大小可能优先于 tree shaking 。这是一个 trade-off 问题 2. 多一个 runtime 开销是否值得,也和项目的规模有关 3. 调试是否复杂跟代码结构有关。Zova 提供了更多的代码规范,代码更加清晰,或许更容易调试一些。反之,原始的 Vue3 并没有对业务架构做出更多的约定,也没有提供现成的最佳实践,代码风格反而难以统一。 |
25
zhennann OP @unco020511 请参见这篇文档:为什么需要 Vue3+IOC: https://zova.js.org/zh/guide/start/why.html
|
26
zhennann OP @gogozs Zova 与 Java 的代码风格有显著的不同,体现在以下两个方面:
1. 更少的装饰器函数:Zova 采用依赖注入与依赖查找相结合的策略,优先使用依赖查找,从而大量减少装饰器函数的使用 2. 更少的类型标注:Zova 优先使用依赖查找可以达到化类型于无形的开发体验,也就是不需要标注类型就可以享受到类型编程的诸多好处,从而让我们的代码始终保持简洁和优雅,进而显著提升开发效率,保证代码质量 |
27
qrobot 73 天前
@zhennann 你说了这么多我实在是看不到任何优点, 无非是把 Spring 这一套强行拿到 前端来, 这非常过度设计.
因为项目足够大, 一个 runtime 的开销非常恐怖, 你自己看看, 现在主流的都是想着怎么去 runtime , 反而你还在里面加 runtime 反其道而行之 webpack 之初就是为了 tree shaking 和 code splitting, 现在你把 tree shaking 的功能完全丢弃了. 其次你的想法很好, 站在项目工程角度上来考虑这个事情, 这些东西我觉得这完全是将简单的东西复杂化. |
29
zhennann OP @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 。若有空可以一试。 |