Typescript中的函数依赖注入DI


依赖性注入是将一个代码单元的逻辑与它的依赖关系分开。在函数的上下文中,它采取的形式是将这些依赖关系建模为依赖函数的参数,并将其作为参数传递(注入)。
我发现DI在以下几个方面提供了价值。

  • 它使mocking或单元测试变得轻而易举,正如你将在后面看到的。
  • 它导致了松散的耦合和易于配置的函数。
  • 一个DI结构,正如建议的那样,并将在这里展示,可以导致有状态和无状态模块的干净分离。

"依赖性 "是一个有点模糊的术语,这可能是DI最令人困惑的部分。
我的意思是,依赖性到底是什么?函数体中的记录器实例是一个依赖关系吗?也许用于获取用户数据的数据库查询对函数的输出至关重要?那么从库中导入并在函数主体中使用的实用函数呢?
记录器和数据库查询总是可以被认为是任何函数的依赖性。
实用函数呢?其实不是,那只是函数逻辑的一部分。

这里有几个发现依赖关系的提示:

  • 如果你需要使用像Jest的 "spy "方法来伪造你正在测试的函数内部的函数调用,你就会看到一个潜在的依赖关系。
  • 如果你的函数所依赖的东西在不同的环境中表现不同或有不同的配置,那就把它签到依赖方。
  • 如果你的函数所依赖的东西是有状态的,我们不要争论,它就是一个依赖关系。

使用高阶函数DI
我们要通过将依赖性作为参数传递给我们的依赖性函数来注入我们的依赖性。
但首先,我想稍微修改一下上面的DI的定义。下面是修改后的版本。
DI首先要在什么是函数的依赖关系和什么是输入之间画一条线,然后注入/传递这些依赖关系,而不必马上传递输入。

  • 依赖注入并不是OOP的专利,它可以在FP中使用HOF轻松实现。
  • 添加一个两层的模块结构,可以使传递依赖关系的繁琐变得更顺畅。
  • 使单元测试成为一项简单的任务。
  • 同样的结构增强了依赖的透明度。
  • 还有比使用承诺和抛出错误更好的方法来模拟异步操作,好的候选者是Task/AsyncEither。


让我们想象一下,我们有一个投票模块,用于对问题进行投票。
我们的模块暴露了一个名为 voteOnIssue 的函数。该函数通过确保以下几点来验证投票。

  • 该问题仍然开放供投票。
  • 用户没有对该问题进行过投票。
  • 用户有资格对该问题进行投票。例如,投票者可能被要求至少年满18岁。

为了获得这些信息,该功能需要查询数据库。查询和记录并不是函数逻辑的一部分,所以我们将把它们作为依赖关系来传递。


interface Dependencies {
  logger: {
    info: (message: string, metadata?: any) => void
    error: (message: string, metadata?: any) => void
    }
    dbPool: Knex
}

export const queryGetIssueData = (
dependencies: Dependencies ) => async issueId => {
const { dbPool, logger } = dependencies try { const query = await dbPool,
.select('*') .from('issues').
.where('id', issueId), return query [0] || null } catch (e) {
logger.error(e.message)
return e } }

export const queryUserVoted = G
dependencies: Dependencies ) => async (issueId, userId): Promise => {
const { dbPool, logger } = dependencies try { const query = await dbPool
.from('votes') .where('userId', userId)
.andWhere('issueId', issueId), return query [0] ? true : false, } catch (e) {
logger.error(e.message) return e


其实对于函数编程是不需要依赖注入的:
编写您的函数以接收所有依赖项,然后编写一个高阶函数,重新导出第一个函数并注入所有依赖项。然后您可以使用第二个功能,但测试第一个功能。

这篇文章的主要重点不是说服别人使用 DI,这无疑引入了一些严谨性和冗长性;
它是关于如何以一种对 FP 友好的方式进行 DI,而不需要拖入类和大量的 OOP。
但是当我试图解释事情时,我发现自己不得不解释为什么将 DI 引入你的代码库是一件好事。
说到严谨,我想说 Javascript 在历史上并没有受到它的影响,因为它在历史上没有用于严肃的后端开发。
现在事情已经发生了变化,严格的领域需要严谨,你真的不想把银行、授权等方面的事情搞砸……但并不是每个模块都需要 DI,每个项目也不需要。