V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
shakaraka
0D
V2EX  ›  分享创造

在写 ts 项目的时候,被依赖注入烦到

  •  
  •   shakaraka ·
    PRO
    · 3 天前 · 2257 次点击

    烦了装饰器那套东西。在 TS 里,那版接近 stage 2 提案语义的装饰器基本已经成了事实标准,用接近 stage 3 语义来实现的又很少,规范本身到现在也没真正完全落地,用起来总有点别扭。

    现在又是函数当道的时代,我就干脆写了一个完全不碰装饰器、不搞反射、不搞 token 注册,只用函数和“引用”来做依赖注入的库。

    目前在公司几个 bun 项目里已经跑了一阵子,感觉还挺顺手。思路有点像 Go 里的 wire —— JS 生态里用代码生成来做依赖注入的比较少,主要还是 Go 那边玩得多。我当时就是看 wire 生成的那些注入样板代码时,突然有了点灵感。

    在这之前,我做 DDD 的时候是完全不用 DI 库、全靠手动注入的,结果光是各种组装和传参就写了七八百行,写到后面整个人都麻了(谁说 JS 不需要依赖注入的?)。最后索性把这套思路实现成了现在这个简单的 IoC 。

    https://github.com/MunMunMiao/dn-ioc

    然后用 AI 翻译了一版 go 版本的,还没在正式项目用上,等以后有机会吧

    https://github.com/MunMunMiao/go-ioc

    18 条回复    2025-12-06 10:52:19 +08:00
    shenqi
        1
    shenqi  
       3 天前
    不太懂你这个啥仓库作用是啥,也没看到太多像 ng 时代的依赖注入的东西。

    我写 react ( function )的时候没有依赖的说法,写 ng 的时候才会有依赖的说法(所以我不再用它)。
    我不知道依赖注入有啥好,只知道不好理解也不方便上手。import 各种的清晰明了易上手。

    参数就是参数,回调方程传递依赖注入其实也是参数,莫名其妙的那些回调参数作为被广泛定义为依赖注入,在我看来就是脱裤子放屁。
    NewYear
        2
    NewYear  
       3 天前
    哈哈,以前记不住 vb 的 UBound ,又用的是记事本编程(当时写 asp 没什么好的 IDE),每个数组的 arr[0]我都用来记录数量,数据从 arr[1]开始,刚好数量=索引号,取最后一个 item 还能少做一次计算。

    编程嘛,主打的就是减少思想负担,不一定要用语言给的那一套(虽然性能没那么好但是方便)。

    最近买了两本 vue 的书籍,看得我愁眉苦脸。。感觉打开方式完全不对,应该从渐进式入门可能才是对的。


    好多东西都这样,别人给的不适应,那就换条路。
    shunia
        3
    shunia  
       3 天前
    2025 年了,IoC 在实际工程代码里面主要是解决什么问题?你的库又是为了解决 IoC 的什么问题?
    shakaraka
        4
    shakaraka  
    OP
    PRO
       3 天前 via iPhone
    @shunia
    @shenqi

    可以完整阅读下我的文档,有碰到疑问点的可以试试我接入的 deepwiki ,你们的疑问基本可以解决
    BingoXuan
        5
    BingoXuan  
       3 天前
    @shenqi
    因为有 Context 。所以所有依赖注入最后变成了读取配置文件构建对象的工厂。Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
    yangzzzzzz
        6
    yangzzzzzz  
       3 天前
    我现在项目都剔除 ts 了 好多地方脱裤子放屁 而且有些人 any 一把梭 完全按照 js 写
    Ketteiron
        7
    Ketteiron  
       3 天前
    我一直觉得,js 的依赖注入完全没必要,除非项目整体是用 oop 思想开发的,例如明明写 js/ts 非要搞一堆 class 。

    装饰器本质上是高阶函数,或者用 oop 的话来说是元编程,但都一样。

    oop 语言渴望函数式,于是它们发明了基于 AOP/动态代理的各种特性,我就不报菜名了,但它们本质上都是同一个东西——高阶函数的仿制品。

    go 的 wire 很微妙地处在可用可不用的位置,是由于语言本身的问题。
    但是 js/ts 是可以完全不使用 class/装饰器/依赖注入等 oop 专属概念的语言。
    你明明提到了函数,但却没把它当一等公民。

    我经常听到的依赖注入的优点之一是便于 mock ,这个在 go 是成立的,在 js/ts 是不成立的。
    为什么要 mock?是为了暂时排除依赖影响,去测试"函数"是否符合预期,那么对于 ts 来说,请直接测试函数本身。如果有外部依赖必须注入,例如数据库连接池、logger ,用工厂函数。
    Ketteiron
        8
    Ketteiron  
       3 天前
    > 结果光是各种组装和传参就写了七八百行
    这就是 nestjs 等框架带来的危害
    DI 本质上是倒转依赖方向,由人工处理拓扑结构转为框架底层去处理,而这样的意义是为了方便地把"函数"注入进来,因为函数必须在一个对象中,DI 的思想是为了 OOP 服务的

    清醒点,你写的是 js/ts ,你可以在任何时候 import 可复用的函数,无需注入"包罗万象"的对象进来
    shakaraka
        9
    shakaraka  
    OP
    PRO
       2 天前
    @Ketteiron #8

    https://chatgpt.com/share/693288fa-fd2c-8001-9022-896259433523

    我根据你说的和 gpt 聊了一下,你看看是你想表达的意思吗
    Zhuzhuchenyan
        10
    Zhuzhuchenyan  
       2 天前
    待过几个外企的面向内部的 App 开发都是 Angular + Nest.js 一把梭

    不可否认的是这两个框架一脉相承的依赖注入系统在 JS/TS 语境下设计的太繁杂了,对初学者上手成本很高,而且内部也经常会讨论这个帖子所指出的问题

    提一个代码之外的视角,框架给出的“有明确主张的、固执已见”的解决方案,包括但不限于上述讨论的依赖注入,能更好的限制代码库贡献者写出的代码,让这些代码更好地满足规约以降低整体的代码审阅和返工的后期成本

    JS/TS 的环境变化太快,日新月异,这也就导致了有的人写代码更新更现代,有的人写代码更老更保守。从团队角度来看,对 lead 的把握团队技术框架,代码规约和实现方式上提出了更高的要求

    “摆烂”的来看,这种戴着镣铐跳舞的技术选择不是不可接受的。
    version
        11
    version  
       2 天前
    我现在都尽量不创造一套东西..
    特别是 npm 包.因为 ai 不懂你的东西是干嘛的..后续 ai 很难帮你优化和编写业务代码
    现在数据库那些.我都尽量使用原生的驱动..第三方 orm 都不搞
    Ketteiron
        12
    Ketteiron  
       2 天前
    @shakaraka #9 差不多这个意思,但它写的例子不对
    export async function getUserById(id: string, repo: UserRepo) {
    return repo.findById(id);
    }
    这种做法只是另一种脱裤子放屁的实现

    当我们用面向对象思想组织代码,会将变量与函数实现放在 class 中,没有全局作用域的语言必须这么做。
    这会导致一个问题,我必须先有一个对象,然后才能调用这个函数,这导致一句著名的话:"你想要得到一根香蕉,但结果却得到了一个拿着香蕉的大猩猩,以及整片丛林",那七八百行就是当前上下文的丛林。

    如果换成偏向函数式的 js 来写,import domain/server/repository 等方式是冗余甚至有害的,因为这些对象有着依赖关系。
    我通常不会主动创建任何不必要对象,函数只是函数,它们可以有一个主人,例如当前的 js 文件本身,等待其他文件对它进行调用,不需要特地装进一个箱子,或者大中小三个箱子。

    像 userService.getUserById() 之类的写法完全没必要,userService 是一个冗余的命名空间
    import { getUserById } from '@/xxx/user' 就行
    绝大多数情况下分层也是没必要的,这反而加大了业务理解难度
    不需要得到这个函数后面的所有隐式依赖对象,简单地得到这个函数本身然后调用它就行
    要得到一根香蕉,只需要执行一个能获得一根香蕉的函数。
    那么所有的依赖关系就只留在互相调用之中,没必要特地去管,尝试无意义的解耦没有太明显的好处。

    一直以来用不用 DI 有争议,我是选择了不使用 DI 的阵营,并且应用于大型项目。
    https://stackoverflow.com/questions/9250851/do-i-need-dependency-injection-in-nodejs-or-how-to-deal-with
    喜欢 DI 的人会使用面向接口而非面向实现更好这个理由,但我一直认为是坏处,面向函数、面向类型/契约编程,需要编写的代码更少且更安全。
    shakaraka
        13
    shakaraka  
    OP
    PRO
       2 天前
    @Ketteiron #12

    你可以推荐哪些 js 、ts 、go 大型项目是使用你说的理念实现的?

    我对于 DDD 和传统 MVC 目前是手到擒来的程度,即使我带着新成员开始新项目想尝试抛开 DI ,但随着项目越来越大,开发人员能力参差不齐(这点我掌控不了),导致乱象满天飞,只能通过一些“固执已见”的方案来约束开发人员。
    shakaraka
        14
    shakaraka  
    OP
    PRO
       2 天前
    @Ketteiron #12

    不过即使是 react 这种所谓纯函数的库,其实也有 provide 、inject 的概念,方式不同(通过 Context ),但概念还在。
    Ketteiron
        15
    Ketteiron  
       2 天前
    @shakaraka #13 你都用 bun 了,没试过 hono/elysia 之类的框架吗。它们更加激进地采用函数式路由+中间件+RPC ,不使用 FP 去开发会极其别扭。它们的代码库本身也是实用性函数式实践的一个良好示范。
    nestjs 等项目依赖装饰器和类来定义结构,而这些较为新颖的框架是 schema 第一,类型第一,函数优先。它们推荐你使用 schema ,配合 prisma/drizzle/kysely 等 ORM 可以得到最良好的体验。
    以 drizzle-orm 举例,定义好表结构,使用 drizzle-zod 可以派生出相应类型,例如一个表字段定义为 varchar({ length: 50 }),派生出的 schema 会自动生成 z.string().max(50),其运行时检验保证错误参数绝不会到达 sql 层,并且建议这份 schema 同时应用在前端表单校验和路由参数校验上,而前端使用框架提供的 RPC 获得与后端接口完全一致的类型,类型是框架反射 schema 类型和当前路由返回值提取出来的,不需要开发者重新写一遍,这套做法极致地实现了 DRY 理念,只需定义一份契约,保障全链路的运行时安全和 typescript 类型安全,并且减少了一堆冗余文件。
    当使用这些框架去开发,已经没必要再分什么层了,分无可分,直接在路由编写业务代码就行。
    而在此之上还有一堆优化空间,多用函数式,理想情况下可以压缩 75%以上代码,并且 bug 更少类型更安全且一致。
    当然要践行起来坑也是不少的,例如我正在考虑把 zod 和 drizzle 换成 arktype 和 kysely 。
    shakaraka
        16
    shakaraka  
    OP
    PRO
       2 天前
    @Ketteiron #15

    我带的团队是选用 bun,elysia,drizzle + DDD 架构开发的,目前已经做了 3 个大型后端项目了,规模的话大概就单单 domain service 的平均数量大概有 7 、80 个,初期我计划使用纯函数没打算使用 DI ,所以后面项目一团糟,现在用了我这个 ioc 就好了很多,因为在敏捷开发的时候,不能够要求团队成员做好代码规范,加上 js 生态就很灵活,所以一发不可收拾。

    所以经过这个事情后,在团队中我认为规范、约束是一等公民。写业务只要按着框架写(类似 spring ),阿猫阿狗写的都差不多,如果是静态语言,那更好,免得像 js 一样写错都可以运行(同样的团队因素)。
    Chuckle
        17
    Chuckle  
       2 天前
    还是更喜欢用装饰器一点,更灵活,堆叠也比函数嵌套调用直观好写,ts 的装饰器本质上也就是把 class 构造器、函数给传了进去,所以既可以 @装饰也可以直接函数调用 遇到需要运行时的动态注入,装饰器也可以封装出更简单的语法糖
    Ketteiron
        18
    Ketteiron  
       2 天前
    @Chuckle 这些都可以用中间件实现,我感觉不到太大好处,还丢了类型安全。
    当然,没有非常通用的库,对于不同场景下的用例可能需要手搓。
    对象和参数就应该是不可变的,装饰器这套隐藏的风险太大,太过依赖"约定大于配置"。

    而且这取决于底层用的框架是什么,nestjs 没得选,hono 之类的框架也没得选。

    如果不用框架,我觉得显式编程对比装饰器多数情况下代码行数会更少,除非项目/库架构基于对象可变性。
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   5857 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 06:19 · PVG 14:19 · LAX 22:19 · JFK 01:19
    ♥ Do have faith in what you're doing.