文章同步在公众号:萌萌哒草头将军,欢迎关注!也可以戳这个沸点
这篇文章,我已经计划了很久,上篇文章中简单的使用了依赖注入模式,但是这仅仅是牛刀小试。
💎 控制反转和依赖注入
因为和依赖注入(DI:dependency injection)密切还有控制反转(Ioc:inversion of control)。这方面最优秀的实践,莫过于spring boot了。
不用担心,下面我们不会深入聊后端,这里仅仅是引出我们的概念。
spring boot中,内置了容器存放注入的组件(spring里称为Bean),并且管理这些组件,在需要使用组件的地方向组件申请使用。
上面这句话,需要你仔细审读。
注入组件的过程就是我们熟悉的依赖注入。
容器管理组件(生命周期)就是控制反转,因为组件的创建和控制权从程序员本身转换到容器了。
如果你还是不理解,我们在举个简单的例子:
很久以前你还是个普通人,柴米油盐,都是你自己操持,但是某一天,你时来运转,发了大财,平地一声雷,陡然而富。你雇佣了两个管家,七八个佣人,从此你只管风花雪月,不用操持柴米油盐了。
你雇佣佣人和管家的过程就是依赖注入,管家和佣人就是容器。而这个结果就是控制反转。
下面这张图也是同样的道理。
好了,我们可以下个定义了,
依赖注入:是一种设计模式,主要用于管理软件组件之间的依赖关系。
控制反转:是一种设计原则,主要将控制权从组件自己转移到外部的容器或框架。
控制反转最大的特点是,我们再也不用使用new关键字创建对象了,因为这部分工作交给容器了
这时候,你肯定会问:react是不是也内置了容器,比如Context?,看起来是,但事实并非如此,因为这个"容器"没有管理组件的能力。
React的Context是React提供的一种在组件之间共享数据的机制。Context使得数据可以在组件树中的多个层级中传递,避免了通过props一层层传递的麻烦。
💎 注解的原理
spring boot还有个令人羡慕的功能,那就是注解了(可能很少有人跟我一样有这种"癖好"吧)。注解功能是依赖注入的一部分,
而注解的本质就是个符合装饰器模式的函数。
例如:我们有个简单的函数,弹出警告信息,
js
function alertMsg(message: string) {
return alert(message);
}
接下来的需求是每次弹出信息的时候记录在日志里。下面是装饰器模式的实现
js
function logAlert(fn: Function) {
return function (...args: any[]) {
console.log(`Calling alertMsg with message: ${args[0]}`);
const result = fn.apply(this, args);
return result;
};
}
如果使用注解的方式,那么我们需要修下参数。
js
// target: 被装饰的类的原型对象
// key: 被装饰的方法名(在这里是 "alertMsg")
// descriptor: 被装饰的方法的属性描述符
function logAlert(
target: any,
key: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling alertMsg with message: ${args[0]}`);
const result = originalMethod.apply(this, args);
return result;
};
return descriptor;
}
class Utils {
@logAlert
alertMsg(message: string) {
return alert(message);
}
}
const instance = new MyClass();
instance.alertMsg('Hello, world!');
装饰器在 JavaScript/TypeScript 中仍处于实验阶段,并不是官方的标准。在某些情况下,装饰器的使用可能需要进行额外的配置或使用特定的工具链来支持。因此,在使用装饰器时,请确保您的开发环境和工具链支持装饰器的语法和功能。
💎 环境准备
好了,所有的铺垫都说完了,我们接下来怎么用控制反转的思想写React组件。
我们主要使用的库是:inversify、inversify-react。后者是对前者的react支持。
inversify是很有名的javascript控制反转库。它提供了容器去管理组件。
如果你十分感兴趣,可以看看这个官方的说明,你会明白管理组件的过程。
好,我们首先下载依赖:
cmd
npm install inversify reflect-metadata inversify-react --save
接着配置下tsconfig.json,开启experimentalDecorators和emitDecoratorMetadata选项。
json
{
"compilerOptions": {
"target": "es5",
"lib": ["es6"],
"types": ["reflect-metadata"],
"module": "commonjs",
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
此时,我们需要下载对应的babel插件
cmd
npm install --save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
如果你是vite项目,需要额外配置下vite,config.ts
js
import { default as reactSupport } from "@vitejs/plugin-react";
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
optimizeDeps: {
include: ["@babel/plugin-proposal-decorators"],
},
plugins: [
reactSupport({
babel: {
parserOpts: {
plugins: ["decorators-legacy", "classProperties"],
},
},
})
],
});
💎 真正的实践
🚀 首先,声明一个组件
js
import { injectable } from "inversify";
@injectable()
export class Utils {
log() {
console.log("data");
}
}
🚀 接着,创建一个容器
js
import { Container } from "inversify";
import { Utils } from "../utils";
// 创建 IoC 容器
const container = new Container();
container.bind<Utils>(Utils).toSelf();
export default container;
🚀 然后,提供容器
我们在入口文件一定要引入:import "reflect-metadata"
,这是inversify的主要依赖。
js
import { Provider } from "inversify-react";
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import "reflect-metadata";
import App from "./App.tsx";
import container from "./Ioc/index.ts";
ReactDOM.render(
<StrictMode>
<Provider container={container}>
<App />
</Provider>
</StrictMode>,
document.getElementById("root")
);
🚀 最后,使用组件
使用组件你可以i根据不同类型选择不同的方式。如果使用hook组件。
js
import { useInjection } from "inversify-react";
import { useEffect } from "react";
import { Utils } from "./utils";
function App() {
const utils = useInjection(Utils);
useEffect(() => {
utils.log();
}, []);
return <>hello</>;
}
export default App;
如果,你使用类组件,那么可以更完美的体验注解的魅力。
js
import { resolve } from "inversify-react";
import React from "react";
import { Utils } from "./utils";
class App extends React.Component {
@resolve
utils!: Utils;
componentDidMount() {
this.utils.log();
}
render(): React.ReactNode {
return <div>hello</div>;
}
}
export default App;
已经初具成效了,下篇文章我们一起搭建一个"逼真"的spring boot框架吧。
好了,今天的分享就这些了,希望可以帮到你。十分感谢您的阅读。