前端框架中的AOP设计
前端框架中的AOP设计
什么是AOP
AOP是一种面向业务过程切面的编程范式,使用AOP的思想可以对各个业务逻辑模块进行隔离,降低各个模块间的耦合度提高可重复利用率,AOP并没有规定其协议的具体实现,可以根据自己的需要进行实现。
如常见的前端全局过滤器、请求后端接口、全局数据映射、中间状态处理等将线性的业务逻辑中的某一阶段抽象出来作为独立的模块,并在在合适的时候切入到更多调用链路中
在前端框架中怎样实现AOP思想
以一段http请求为例,如一下代码中包含了对请求头的处理、数据的转换、请求域名的添加、响应的处理
async function requestAPI(url: string, init: RequestInit) {
init.headers = init.headers ?? {
}
init.headers = {
'contentType': 'application/x-www-form-urlencoded; charset=UTF-8', ...init.headers }
if (init.body === 'object' && !Array.isArray(init.body)) {
init.body = JSON.stringify(init.body ?? {
})
}
const regex = new RegExp('(^https:)|(^http:)\/\/([^\/\?]+)', 'g')
if (!regex.test(url)) {
url = 'https:xxx.com' + url
}
try {
const response = await fetch(url, init)
const res = await response.json()
if (res.code === 200) {
return res.data
} else {
throw new ApiException({
code: res.code,
msg: res.msg,
url
})
}
} catch (error) {
if (error instanceof ApiException) {
throw error
}
// ...处理异常
}
}
在这种写法中如果请求头需要调整,需要对请求参数或响应做一些处理则需要构建新的请求方法或者对原有的方法做一些改造,需要做节流、防抖之类的处理也需要对这个函数做改造或在此封装,那么用AOP的思想来处理应当怎么实现呢?
interface HttpOption extends RequestInit {
url: string
body?: any
}
interface HttpResponse<T> {
code: number
data: T
msg: string
}
function DomainDecorators() {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const fn = descriptor.value as (options: HttpOption) => Promise<Response>
descriptor.value = (options: {
domain: string
} & HttpOption) => {
if (options?.domain) {
const regex = new RegExp('(^https:)|(^http:)\/\/([^\/\?]+)', 'g')
if (!regex.test(options.url)) {
options.url = options.domain + options.url
}
}
return fn.call(target, options)
}
}
}
function OptionsDecorators() {
return function (target: HttpClient, propertyKey: string, descriptor: PropertyDescriptor) {
const fn = descriptor.value as (options: HttpOption) => Promise<Response>
descriptor.value = (options: HttpOption) => {
options.headers = {
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', ...options.headers }
if (options.body === 'object' && !Array.isArray(options.body)) {
options.body = JSON.stringify(options.body ?? {
})
}
return fn.call(target, options)
}
}
}
function ExceptionDecorators() {
return function (target: HttpClient, propertyKey: string, descriptor: PropertyDescriptor) {
const fn = descriptor.value as (options: HttpOption) => Promise<HttpResponse<any>>
descriptor.value = async (options: HttpOption) => {
const res = await fn.call(target, options)
if (res.code === 200) {
return res.data
} else {
throw new ApiException({
code: res.code,
msg: res.msg,
url: options.url
})
}
}
}
}
abstract class HttpBase {
get options(): {
domain?: string
} & RequestInit {
return {
mode: 'cors',
credentials: 'include',
cache: 'no-cache',
redirect: 'follow'
}
}
post<T>(options: HttpOption): Promise<HttpResponse<T>> {
options.method = 'POST'
return fetch(options.url, options).then((res) => res.json())
}
get<T>(options: HttpOption): Promise<HttpResponse<T>> {
options.method = 'GET'
return fetch(options.url, options).then((res) => res.json())
}
}
class HttpClient extends HttpBase {
@DomainDecorators()
@OptionsDecorators()
@ExceptionDecorators()
post<T>(options: HttpOption): Promise<HttpResponse<T>> {
return super.post(options)
}
}
这里我们可以将请求头处理部分和相应部分抽象出来,作为一个切面织入实现的请求类中,将参数处理、域名添加、响应的异常几部分分离出来,在需要的时候任意组装,各个模块都是为请求服务,但彼此之前在没有关联和依赖性,只要装饰的对象的方法签名相同那么都复用这些函数~