发布于 2026-01-06 2 阅读
0

React中何时以及为何需要依赖注入

React中何时以及为何需要依赖注入

我们的 React 应用由许多小型组件或模块组成。我们编写的组件有时会相互依赖。随着应用规模的增长,妥善管理组件间的这些依赖关系变得至关重要。依赖注入是一种常用的模式,可以用来解决这个问题。

本文我们将讨论

  • 何时需要应用依赖注入模式
  • 使用高阶组件(HOC)进行依赖注入
  • 使用 React Context 进行依赖注入

注意:如果您之前没有依赖注入方面的知识,我建议您阅读以下博客文章。

让我们来看下面的例子。

// app.js
function App() {
  const [webSocketService, setwebSocketServicet] = React.useState({});
  React.useEffect(() => {
    // initialize service
    setwebSocketServicet({
      user: `some user`,
      apiKey: `some string`,
      doStuff: () => console.log("doing some function")
    });
  }, []);
  return (
    <div>
      <B socket={webSocketService} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

这里我们有一个App组件,它正在初始化一个服务,并将引用作为 props 传递给它的子组件。

// B.js
function B(props) {
  return (
    <div>
      <A {...props} />
    </div>
  );
}

// A.js
function A(props) {
  // Do something with web socket
  const doWebSocket = () => {
    props.socket.doStuff();
  };
  return (
    <div>
      <button onClick={() => doWebSocket()}>Click me</button>
      {props.children}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

组件B接收来自父组件的 propsApp并将其传递给子组件AB子组件本身并不处理传递的 props。我们的websocket实例应该以某种方式到达A它被使用的组件。这是一个非常基础的示例应用程序,但在实际场景中,当我们有许多相互嵌套的组件时,我们需要将此属性一直传递下去。例如

<ExampleComponentA someProp={someProp}>
  <X someProp={someProp}>
    <Y someProp={someProp}>
      //.... more nesting 
      //... finally Z will use that prop
      <Z someProp={someProp} /> 
    </Y>
  </X>
</ExampleComponentA>

Enter fullscreen mode Exit fullscreen mode

许多组件都充当代理,将此属性传递给它们的子组件。这也降低了代码的可测试性,因为当我们为这些组件(X 或 Y)编写测试时,someProp即使该属性的唯一目的就是将其传递给子组件,我们也必须进行模拟。

现在让我们看看如何使用高阶组件进行依赖注入来解决这个问题。

让我们创建一个名为 `.yml` 的文件deps.js,该文件中将包含两个函数。

import React from "react";

let dependencies = {};

export function register(key, dependency) {
  dependencies[key] = dependency;
}

export function fetch(key) {
  if (dependencies[key]) return dependencies[key];
  console.log(`"${key} is not registered as dependency.`);
}
Enter fullscreen mode Exit fullscreen mode

在这个dependencies对象中,我们将存储所有依赖项的名称和值。该register函数用于注册依赖项,而fetch另一个函数则根据键获取依赖项。

现在我们将创建一个高阶组件 (HOC),该组件返回一个包含我们注入的属性的组合组件。

export function wire(Component, deps, mapper) {
  return class Injector extends React.Component {
    constructor(props) {
      super(props);
      this._resolvedDependencies = mapper(...deps.map(fetch));
    }
    render() {
      return (
        <Component
          {...this.state}
          {...this.props}
          {...this._resolvedDependencies}
        />
      );
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

在我们的wire函数中,我们传入一个组件Component、一个依赖项数组dependencies和一个mapper对象,它会返回一个Injected带有依赖项的新组件。我们在构造函数中查找并映射这些依赖项。我们也可以在lifecycle钩子函数中完成此操作,但为了简单起见,我们现在先使用构造函数。

好的,让我们回到第一个例子。我们将对App组件进行以下更改。

+ import { register } from "./dep";

function App() {
  const [webSocketService, setwebSocketServicet] = React.useState(null);
  React.useEffect(() => {
    setwebSocketServicet({
      user: `some user`,
      apiKey: `some string`,
      doStuff: () => console.log("doing some function")
    });
  }, [webSocketService]);
+ if(webSocketService) {
+   register("socket", webSocketService);
+   return <B />;
+ } else {
+   return <div>Loading...</div>;
+ }
}
Enter fullscreen mode Exit fullscreen mode

我们初始化了 WebSocket 服务并将其注册到register函数中。现在,在我们的A组件中,我们进行以下更改以将其连接起来。

+const GenericA = props => {
+  return (
+    <button onClick={() => console.log("---->>", +props.socket.doStuff())}>
+      Push me
+    </button>
+  );
+};
+const A = wire(GenericA, ["socket"], socket => ({ socket }));
Enter fullscreen mode Exit fullscreen mode

就这样。现在我们不用担心代理传递的问题了。这样做还有另一个好处。JavaScript 中典型的模块系统都带有缓存机制。

模块在首次加载后会被缓存。这意味着(除其他事项外),如果每次调用 `require('foo')` 都能解析到同一个文件,那么每次调用都会返回完全相同的对象。

多次调用 `require('foo')` 不会导致模块代码多次执行。这是一个重要的特性。有了它,就可以返回“部分完成”的对象,从而允许加载传递依赖项,即使它们会导致循环引用。

***摘自 Node.js 文档

这意味着我们可以初始化依赖项,它们会被缓存,这样我们就可以在多个地方注入它们而无需再次加载。导出此模块时,我们实际上是在创建一个单例。

但现在是 2019 年了,我们想使用 Context API 对吧?好的,那么让我们来看看如何使用 React Context 进行依赖注入。

注:如果您想了解更多关于 SOLID 原则如何应用于 React 的信息,请查看我之前的文章(链接在此) 。

让我们创建一个名为的文件context.js


import { createContext } from "react";

const Context = createContext({});

export const Provider = Context.Provider;
export const Consumer = Context.Consumer;

Enter fullscreen mode Exit fullscreen mode

现在,在我们的App组件中,我们可以使用上下文提供程序来代替注册函数。那么,让我们进行更改吧。

+import { Provider } from './context';

function App() {
  const [webSocketService, setwebSocketServicet] = React.useState(null);
  React.useEffect(() => {
    setwebSocketServicet({
      user: `some user`,
      apiKey: `some string`,
      doStuff: () => console.log("doing some function")
    });
  }, []);

  if (webSocketService) {
+    const context = { socket: webSocketService };
    return (
+      <Provider value={ context }>
        <B />
+      </Provider>
    )
  } else {
    return <div>Loading...</div>;
  }
}
Enter fullscreen mode Exit fullscreen mode

现在,在我们的A组件中,我们不再需要连接高阶组件,而是直接使用上下文消费者。

function A(props) {
  return (
    <Consumer>
      {({ socket }) => (
        <button onClick={() => console.log(socket.doStuff())}>Click me</button>
      )}
    </Consumer>
  );
}
Enter fullscreen mode Exit fullscreen mode

好了,这就是我们使用 React Context 进行依赖注入的方法。

最后想说的话

依赖注入(Dependency Injection, DI)被许多 React 库所采用,其中 React Router 和 Redux 尤为突出。DI在 JavaScript 世界中是一个棘手的问题。学习这些技术不仅能提升 JavaScript 开发水平,还能促使我们在构建大型应用时进行更深入的思考。希望您喜欢这篇文章。请关注我并点赞哦 ;)

下次再见。

*** 注:本文仍在撰写中,我会持续更新内容。如果您能提供任何反馈意见,我将不胜感激。***

文章来源:https://dev.to/shadid12/when-and-why-you-should-do-dependency-injection-in-react-33pa