【typescript】依赖注入与反射框架实现原理

前言

  • 最近感觉自己水平再度上升,研究起了依赖注入与反射框架。后来了解了原理后感觉不过如此。。于是又跑到另一个问题上,这玩意到底是解决啥问题的?啥时候适合去使用?

反射框架

  • 反射框架和依赖注入并不是一回事,虽然他们喜欢一起使用。
  • 这个思想貌似起源于java,因为java可以通过注解+反射去拿类上的方法之类。
  • 一般js里是结合装饰器去使用,通过装饰器给类标记,另一边运行时去收集标签,在适当时候去处理对应内容。
  • 装饰器主要还是靠reflect-metadata,这个库在Reflect上加上了几个api,其一方面是用个map去记录标签,一方面会用Object.defineProperty的方式增加类的静态方法或者类的原型方法。
  • 装饰器创建
  • 依赖ts特性即可,毕竟他会传来target,你可以存起来在别的地方去使用它:
export function Controller(path = ""): ClassDecorator {
    
    
	return (target: object) => {
    
    
		Reflect.defineMetadata(CONTROLLER_METADATA, path, target);
	};
}

export function createMethodDecorator(method: HttpMethod = "get") {
    
    
	return (path = "/"): MethodDecorator =>
		// target:当前类实例,name:当前函数名,descriptor:当前属性(函数)的描述符
		(target: object, name: string, descriptor: PropertyDescriptor) => {
    
    
			Reflect.defineMetadata(
				ROUTE_METADATA,
				{
    
     type: method, path },
				descriptor.value //这个是函数可以运行
			);
		};
}

export function createParamDecorator(type: Param) {
    
    
	return (key?: string): ParameterDecorator =>
		// target:当前类实例,name:当前函数名,index:当前函数参数顺序
		(target: object, name: string, index: number) => {
    
    
			const preMetadata =
				Reflect.getMetadata(PARAM_METADATA, target, name) || [];
			const newMetadata = [{
    
     key, index, type }, ...preMetadata];

			Reflect.defineMetadata(PARAM_METADATA, newMetadata, target, name);
		};
}

export function Parse(type: Parse): ParameterDecorator {
    
    
	return (target: object, name: string, index: number) => {
    
    
		const preMetadata =
			Reflect.getMetadata(PARAM_METADATA, target, name) || [];
		const newMetadata = [{
    
     type, index }, ...preMetadata];

		Reflect.defineMetadata(PARSE_METADATA, newMetadata, target, name);
	};
}

export const Get = createMethodDecorator("get");
export const Post = createMethodDecorator("post");
export const Body = createParamDecorator("body");
export const Headers = createParamDecorator("headers");
export const Cookies = createParamDecorator("cookies");
export const Query = createParamDecorator("query");
  • 在框架运行时,一般都会进行收集标签,比如这里收集了标签后实际就是拼express的路由,做了个中间件而已。
import {
    
     Express, Router } from "express";
import {
    
    
	CONTROLLER_METADATA,
	ROUTE_METADATA,
	PARAM_METADATA,
	PARSE_METADATA,
} from "./decorators";
import {
    
     RouteType, handlerFactory } from "./utils";

function register(
	controllerStore: Record<string, any>,
	rootPath: string,
	app: Express
) {
    
    
	const router = Router();
	Object.values(controllerStore).forEach((instance) => {
    
    
		const controllerMetadata: string = Reflect.getMetadata(
			CONTROLLER_METADATA,
			instance.constructor
		);
		console.log(controllerMetadata, "kkk");

		const proto = Object.getPrototypeOf(instance);
		//这个是拿定义类上的方法
		const routeNameArr = Object.getOwnPropertyNames(proto).filter(
			(n) => n !== "constructor" && typeof proto[n] === "function"
		);
		console.log(routeNameArr, "aaa");
		// 拿到方法后遍历方法获取是否打了标签
		routeNameArr.forEach((routeName) => {
    
    
			const routeMetadata: RouteType = Reflect.getMetadata(
				ROUTE_METADATA,
				proto[routeName]
			);
			console.log(routeMetadata, "jjj");
			const {
    
     type, path } = routeMetadata;
			const handler = handlerFactory(
				proto[routeName],
				Reflect.getMetadata(PARAM_METADATA, instance, routeName),
				Reflect.getMetadata(PARSE_METADATA, instance, routeName)
			);
			// 这里进行拼路由
			router[type](controllerMetadata + path, handler);
		});
	});

	app.use(rootPath, router);
}

export default register;
  • 在拿到参数装饰器上,可以对传来的req res赋能:
export function extractParameters(
	req: Request,
	res: Response,
	next: NextFunction,
	paramArr: ParamType[] = [],
	parseArr: ParseType[] = []
) {
    
    
	if (!paramArr.length) return [req, res, next];

	const args = [];

	paramArr.forEach((param) => {
    
    
		const {
    
     key, index, type } = param;
		// console.log(req.body, req.query);
		switch (type) {
    
    
			case "query":
				args[index] = key ? req.query[key] : req.query;
				break;
			case "body":
				args[index] = key ? req.body[key] : req.body;
				break;
			case "params":
				args[index] = key ? req.params[key] : req.params;
				break;
			case "headers":
				args[index] = key
					? req.headers[key.toLowerCase()]
					: req.headers;
				break;
			case "cookies":
				args[index] = key ? req.cookies[key] : req.cookies;
				break;
			default:
				args[index] = res;
				break;
		}
	});

	parseArr.forEach((parse) => {
    
    
		const {
    
     type, index } = parse;
		switch (type) {
    
    
			case "number":
				args[index] = +args[index];
				break;
			case "string":
				args[index] = args[index] + "";
				break;
			case "boolean":
				args[index] = Boolean(args[index]);
				break;
		}
	});

	args.push(req, res, next);
	return args;
}
  • 这样一个简易的ts反射框架就实现了。
  • 这玩意有啥用?最显而易见的就是开发者在开发时会少写很多东西,跟配好配置项调用比其实没太大区别。感觉是给广大开发者书写上增加一些乐趣。

依赖注入

  • 这个实现我也是研究了很长时间,后来发现原来尼玛就是拿个容器把所有类实例化后甩进map,谁要用再从map里拿就行了。
  • 于是我就一直想这玩意有啥用。。。。
  • 很多人吹依赖注入说这个是本来是依赖底层的,然后你要底层改了之后上层全部得改。还举了个例子,比如底层依赖本来要1个参数现在需要2个参数,你得一遍遍传到底层,如果参数有改动,那么就把所有上层全部改了一遍。我想说整个从头到尾的对象或者全局的对象做配置项进行传不就完事了,就算你参数有变动也只要改自己的加全局对象即可。所以这玩意真能解决什么问题我还没感觉到。
  • 依赖注入的实现方式就不怎么细说了,相信看了第一句话肯定都能实现出来。对于用装饰器实现的同学,顺便提一句,如果在构造器注入时没使用装饰器,那么会编译出一个默认的token,需要编译下看看,这样就可以取到没编译的token。如果用了Inject的装饰器,那么肯定是以Inject的token为准。Inject让你对token起名字主要为了防止你想注入来自相同类型的类导致在map上同名。

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/116175015