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

140 天前
 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

2509 次点击
所在节点    Node.js
29 条回复
lisongeee
140 天前
我建议先看 1 坤月之前发布在掘金的相同文章的评论

https://juejin.cn/post/7369113568573292556#comment
zhennann
140 天前
@lisongeee 不理解你要表达的是什么
LuckyLauncher
140 天前
Vue/React 社区为什么不引入 IoC 容器是有原因的

在前端框架领域没有 IoC 的占有率反而吊打有 IoC 的,前端真的需要 IoC 吗
wuyiccc
140 天前
node 后台用 ioc 可以理解,前端页面为什么要用 ioc 呢,会想 springboot 一样随便打开一个页面就会扫描所有的 class 么?
shimada666
140 天前
qrobot
140 天前
@wuyiccc 你还别说, 我还真做了一个这样的东西, 叫做 componentScan
qrobot
140 天前
@qrobot 特定场景下 componentScan 很好用, 但是 OP 的 IoC 就有点本末倒置
zhennann
140 天前
@wuyiccc 在 Zova 中,装饰过的 class 在初始化时就自动注册到系统中了,不需要扫描
zhennann
140 天前
@qrobot 前端是异步体系,许多模块都是按需异步加载的,采用 componentScan 不能解决所有问题。在 Zova 中,装饰过的 class 在初始化时就自动注册到系统中了,不需要扫描
calmbinweijin
139 天前
@lisongeee 感谢,这边看见了《我们团队是如何用好 vue3 setup 组合式 API 的?》感觉很实用,之前一直以 vue3 的组合式 API 开发 vue2 的选项式代码。感觉才有点理解了组合式 API
ZGame
139 天前
@zhennann 对于 vue 来说 ioc 没有意义, 我可以使用他自身或者第三方的 store,他还可以构造 dispatch action 等概念。等于原生 js 或者 ts 来说 那第三方的 ioc 可选择余地就更多了。 而如果你希望引入 class 概念对于业务逻辑进行封装... emmm, 其实也没错, 但是 vue 目前趋势是 hooks+对应 api 去解耦 和限制数据。 所以你的 ioc 来说,是某种程度上个可以代替相关逻辑 但是契合度太低了
ZGame
139 天前
scope 概念很好 但是感觉 class 契合度太低了。
zhennann
138 天前
@ZGame 为什么 class 契合度太低了?可否再详细说说?
ZGame
138 天前
@zhennann 因为 hooks 某种程度上就是通过函数式的方式代替 class 的业务逻辑的。而且因为有约束处理的好的话 入参出参 副作用这些一定程度上能代替 class,而且如果我不用到 scope 的话 为啥我不直接用第三方的 store 来存储数据 这样数据流单向而且更清晰, 所以我觉得除非有一些案例,能很简单的说非这样注入不可的,那我觉得很难引入到里面。 如果纯 js, ts 底层库的话,而不关联 vue,react 这类框架使用的话 ,应该有一定价值
KuroNekoFan
138 天前
太可怕了,java 人
juzisang
138 天前
很多页面仔根本不需要也不理解什么是大型项目,什么是解耦合,也没什么业务逻辑要写。
页面里最复杂的逻辑基本都是做做动效,拉拉数据完了,强行加入 ioc 确实是增加复杂度。
juzisang
138 天前
vue 这些框架,感觉只是提供了一个视图层面的规范和约束,但是在业务逻辑方面,没啥最佳实践。
hook 和 class 在代码在组织业务逻辑方面,感觉没啥区别,只是写法不同而已。
反倒是 class 实际更契合 js 的语法特性,写起来也更方便。

在维护一个长期项目上,由于前端框架更迭太快了,时间跨度可能有好几年。
这个时候,耦合太多这些视图框架的特性在业务代码里,后续想更新甚至更换视图框架,都会很麻烦。
WispZhan
138 天前
@juzisang 说的挺中肯

对于大部分前端切图仔而言,完全没必要用 IoC ,甚至他们都不理解为啥要弄得这么复杂。
novaline
138 天前
强行 IoC
qrobot
138 天前
@zhennann #9L

举个例子
```
import c1 from "xxx";


class Demo {
@Autowired
private: b2;
}

class Demo2 {

}
```

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

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

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

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

© 2021 V2EX