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>
);
}
这里我们有一个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>
);
}
组件B接收来自父组件的 propsApp并将其传递给子组件A。B子组件本身并不处理传递的 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>
许多组件都充当代理,将此属性传递给它们的子组件。这也降低了代码的可测试性,因为当我们为这些组件(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.`);
}
在这个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}
/>
);
}
};
}
在我们的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>;
+ }
}
我们初始化了 WebSocket 服务并将其注册到register函数中。现在,在我们的A组件中,我们进行以下更改以将其连接起来。
+const GenericA = props => {
+ return (
+ <button onClick={() => console.log("---->>", +props.socket.doStuff())}>
+ Push me
+ </button>
+ );
+};
+const A = wire(GenericA, ["socket"], socket => ({ socket }));
就这样。现在我们不用担心代理传递的问题了。这样做还有另一个好处。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;
现在,在我们的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>;
}
}
现在,在我们的A组件中,我们不再需要连接高阶组件,而是直接使用上下文消费者。
function A(props) {
return (
<Consumer>
{({ socket }) => (
<button onClick={() => console.log(socket.doStuff())}>Click me</button>
)}
</Consumer>
);
}
好了,这就是我们使用 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