JavaScript 中的依赖注入

编写面对不断变化的需求具有弹性的代码需要有意识地应用实现这一目标的技术。

在本文中,我们将探讨依赖注入作为这些技术之一。

看看下面的代码片段。

1
2
3
4
5
const getData = async (url) => {
const response = await fetch(url);
const data = await response.json();
return data;
};

此函数使用 Fetch API 通过网络检索资源并返回它。虽然这可行,但从干净且可维护的代码角度来看,这里有很多事情可能会出错。

  • 如果我们的需求将来发生变化,并且我们决定用另一个 HTTP 客户端(例如 Axios)替换 Fetch API,我们将必须修改整个函数才能与 Axios 一起使用。
  • Fetch API 是浏览器中的全局对象,在我们要运行测试的 Node.js 等环境中不可用或可能无法完全按照预期工作。
  • 在测试时,我们可能不想从网络上实际检索资源,但目前还没有办法做到这一点。

这就是依赖注入发挥作用的地方。

其核心是,依赖注入是从外部提供我们的代码所需的依赖项,而不是像我们在上面的示例中所做的那样,让我们的代码直接构造和解析依赖项。我们将代码所需的依赖项作为参数传递给 getData 函数。

1
2
3
4
5
6
7
8
9
10
const getData = async (fetch, url) => {
const response = await fetch(url);
const data = await response.json();
return data;
};

(async => {
const resourceData = await getData(window.fetch, "https://myresourcepath");
//do something with resourceData
})()

依赖注入背后的目的是实现关注点分离。这使得我们的代码更加模块化、可重用、可扩展和可测试。

javascript 的核心是对象和原型,因此我们可以通过函数式或面向对象的方式进行依赖注入。

JavaScript 的函数式编程特性(如高阶函数和闭包)使我们能够优雅地实现依赖注入。

1
2
3
4
const fetchResource = (httpClient) => (url) =>
httpClient(url)
.then((data) => data.json)
.catch((error) => console.log(error));

fetchResource 函数获取 HTTP 客户端的一个实例,并返回一个接受 URL 参数并对资源发出实际请求的函数。

1
2
3
4
5
6
7
8
9
10
import axios from "axios";

const httpClient = axios.create({
baseURL: "https://mybasepath",
method: "POST",
headers: { "Access-Control-Allow-Origin": "*" },
});

const getData = fetchResource(httpClient);
getData("/resourcepath").then((response) => console.log(response.data));

我们用 Axios 替换了原生的 fetch,一切仍然正常,无需干预内部实现。在这种情况下,我们的代码不直接依赖于任何特定的库或实现。因为我们可以轻松地替换另一个库。

接收依赖项的对象(在本例中为函数)通常称为客户端,而被注入的对象称为服务。

服务可能需要跨代码库的不同配置。由于我们的客户不关心服务的内部实现或配置,因此我们可以像上面所做的那样以不同的方式预配置服务。

依赖注入使我们能够将代码(业务逻辑)与库、框架、数据库、ORM 等外部组件的更改隔离开来。通过适当的关注点分离,测试变得简单明了。

我们可以消除依赖关系,并针对独立于外部组件的多个理想和边缘情况测试我们的代码。

在更复杂的用例中,通常是更大的项目,手动进行依赖项注入根本无法扩展,并且会带来全新的复杂性。我们可以利用依赖注入容器的强大功能来解决这个问题。宽松地说,依赖项注入容器包含依赖项以及创建这些依赖项的逻辑。你向容器请求服务的新实例,它会解析依赖关系,构造对象并将其返回。

有许多 Javascript 依赖注入容器库。我个人最喜欢的是 TypeDIInversifyJS。以下示例演示了 TypediJavaScript 的基本用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Container } from "typedi";

class ExampleClass {
print() {
console.log("I am alive!");
}
}

/** 从 TypeDI 请求 ExampleClass 的实例。 */
const classInstance = Container.get(ExampleClass);

/** 我们收到了一个 ExampleClass 实例并准备好使用它。 */
classInstance.print();

依赖注入技术跨越了不同的编程语言。作为一般经验法则,依赖注入可以使用允许将函数和对象作为参数传递的语言来完成。一些流行的 HTTP 框架(例如 NestJsFastAPI)带有内置的依赖注入系统。