如何利用AOP+IOC思想解构前端项目开发
共 12379字,需浏览 25分钟
·
2020-11-12 19:43
作者:evio
来源:SegmentFault 思否社区
本文将通过 TypeClient 架构来阐述如何利用AOP+IOC思想来解构前端项目的开发。
首先声明,AOP+IOC思想的理解需要有一定的编程架构基础。目前,这两大思想使用的场景,基本都在nodejs端,在前端的实践非常少。我本着提供一种新的项目解构思路的想法,而非推翻社区庞大的全家桶。大家看看就好,如果能给你提供更好的灵感,那么再好不过了,非常欢迎交流。
以下我们将以 TypeClient 的 React 渲染引擎为例。
AOP
一种面向切面编程的思想。它在前端的表现是前端的装饰器,我们可以通过装饰器来拦截函数执行前与执行后的自定义行为。
AOP的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后, 再通过“动态织入”的方式掺入业务逻辑模块中。AOP的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块。
以上是网络上对AOP的简单解释。那么实际代码也许是这样的
@Controller()
class Demo {
@Route() Page() {}
}
复制代码
但很多时候,我们仅仅是将某个class下的函数当作一个储存数据的对象而已,而在确定运行这个函数时候拿出数据做自定义处理。可以通过 reflect-metadata 来了解更多装饰器的作用。
IOC
Angular难以被国内接受很大一部分原因是它的理念太庞大,而其中的DI(dependency inject)在使用时候则更加让人迷糊。其实除了DI还有一种依赖注入的思想叫 IOC。它的代表库为 inversify。它在github上拥有6.7K的star数,在依赖注入的社区里,口碑非常好。我们可以先通过这个库来了解下它对项目解构的好处。
例子如下:
@injectable()
class Demo {
@inject(Service) private readonly service: Service;
getCount() {
return 1 + this.service.sum(2, 3);
}
}
复制代码
当然,Service已经优先被注入到inversify的container内了,才可以通过 TypeClient 这样调用。
重新梳理前端项目运行时
一般地,前端项目会经过这样的运行过程。
通过监听hashchange或者popstate事件拦截浏览器行为。 设定当前获得的window.location 数据如何对应到一个组件。 组件如何渲染到页面。 当浏览器URL再次变化的时候,我们如何对应到一个组件并且渲染。
重新审视服务端路由体系
改造路由设计
如何解析路由字符串规则? 如何利用这个规则快速匹配到对应的回调函数?
http.get('/:id(d+)', () => console.log(1));
http.get('/1234', () => console.log(2));
复制代码
TypeClient 的路由设计
import React from 'react';
import { Controller, Route, Context } from '@typeclient/core';
import { useReactiveState } from '@typeclient/react';
@Controller('/api')
export class DemoController {
@Route('/test')
TestPage(props: Reat.PropsWithoutRef) {
const status = useReactiveState(() => props.status.value);
returnHello world! {status};
}
}
// --------------------------
// 在index.ts中只要
app.setController(DemoController);
// 它就自动绑定了路由,同时页面进入路由 `/api/test` 的时候
// 就会显示文本 `Hello world! 200`。
复制代码
路由生命周期
beforeCreate 页面开始加载 created 页面加载完成 beforeDestroy 页面即将销毁 destroyed 页面已经销毁
import React from 'react';
import { Controller, Route, Context } from '@typeclient/core';
import { useReactiveState } from '@typeclient/react';
@Controller('/api')
export class DemoController {
@Route('/test')
TestPage(props: Reat.PropsWithoutRef) {
const status = useReactiveState(() => props.status.value);
useContextEffect(() => {
console.log('路由加载完成了');
return () => console.log('路由被销毁了');
})
returnHello world! {status};
}
}
复制代码
中间件设计
const middleware = async (ctx, next) => {
// ctx.....
await next();
}
复制代码
import React from 'react';
import { Controller, Route, Context, useMiddleware } from '@typeclient/core';
import { useReactiveState } from '@typeclient/react';
@Controller('/api')
export class DemoController {
@Route('/test')
@useMiddleware(middleware)
TestPage(props: Reat.PropsWithoutRef) {
const status = useReactiveState(() => props.status.value);
useContextEffect(() => {
console.log('路由加载完成了');
return () => console.log('路由被销毁了');
})
returnHello world! {status};
}
}
复制代码
设计周期状态管理 - ContextStore
import React from 'react';
import { Controller, Route, Context, useMiddleware, State } from '@typeclient/core';
import { useReactiveState } from '@typeclient/react';
@Controller('/api')
export class DemoController {
@Route('/test')
@useMiddleware(middleware)
@State(createState)
TestPage(props: Reat.PropsWithoutRef) {
const status = useReactiveState(() => props.status.value);
const count = useReactiveState(() => props.state.count);
const click = useCallback(() => ctx.state.count++, [ctx.state.count]);
useContextEffect(() => {
console.log('路由加载完成了');
return () => console.log('路由被销毁了');
})
returnHello world! {status} - {count};
}
}
function createState() {
return {
count: 0,
}
}
复制代码
// test.ts
import { reactive } from '@vue/reactity';
export const data = reactive({
count: 0,
})
复制代码
import React, { useCallback } from 'react';
import { useReactiveState } from '@typeclient/react-effect';
import { data } from './test';
function TestComponent() {
const count = useReactiveState(() => data.count);
const onClick = useCallback(() => data.count++, [data.count]);
return{count}
}
复制代码
利用IOC思想解构项目
Controller 服务解构
import { Service } from '@typeclient/core';
@Service()
export class MathService {
sum(a: number, b: number) {
return a + b;
}
}
复制代码
import React from 'react';
import { Controller, Route, Context, useMiddleware, State } from '@typeclient/core';
import { useReactiveState } from '@typeclient/react';
import { MathService } from './service.ts';
@Controller('/api')
export class DemoController {
@inject(MathService) private readonly MathService: MathService;
@Route('/test')
@useMiddleware(middleware)
@State(createState)
TestPage(props: Reat.PropsWithoutRef) {
const status = useReactiveState(() => props.status.value);
const count = useReactiveState(() => props.state.count);
const click = useCallback(() => ctx.state.count++, [ctx.state.count]);
const value = this.MathService.sum(count, status);
useContextEffect(() => {
console.log('路由加载完成了');
return () => console.log('路由被销毁了');
})
returnHello world! {status} + {count} = {value};
}
}
function createState() {
return {
count: 0,
}
}
复制代码
Component 解构
import React from 'react';
import { Component, ComponentTransform } from '@typeclient/react';
import { MathService } from './service.ts';
@Component()
export class DemoComponent implements ComponentTransform {
@inject(MathService) private readonly MathService: MathService;
render(props: React.PropsWithoutRef<{ a: number, b: number }>) {
const value = this.MathService.sum(props.a, props.b);
return{value}
}
}
复制代码
import React from 'react';
import { Controller, Route, Context, useMiddleware, State } from '@typeclient/core';
import { useReactiveState } from '@typeclient/react';
import { MathService } from './service.ts';
import { DemoComponent } from './component';
@Controller('/api')
export class DemoController {
@inject(MathService) private readonly MathService: MathService;
@inject(DemoComponent) private readonly DemoComponent: DemoComponent;
@Route('/test')
@useMiddleware(middleware)
@State(createState)
TestPage(props: Reat.PropsWithoutRef) {
const status = useReactiveState(() => props.status.value);
const count = useReactiveState(() => props.state.count);
const click = useCallback(() => ctx.state.count++, [ctx.state.count]);
const value = this.MathService.sum(count, status);
const Demo = useComponent(this.DemoComponent);
useContextEffect(() => {
console.log('路由加载完成了');
return () => console.log('路由被销毁了');
})
return;
Hello world! {status} + {count} = {value}
}
}
function createState() {
return {
count: 0,
}
}
复制代码
Middleware 解构
import { Context } from '@typeclient/core';
import { Middleware, MiddlewareTransform } from '@typeclient/react';
import { MathService } from './service';
@Middleware()
export class DemoMiddleware implements MiddlewareTransform {
@inject(MathService) private readonly MathService: MathService;
async use(ctx: Context, next: Function) {
ctx.a = this.MathService.sum(1, 2);
await next();
}
}
复制代码
为react新增Slot插槽概念
const { Provider, Consumer } = useSlot(ctx.app);
"foo">provider data "foo">placeholder
复制代码
// template.tsx
import { useSlot } from '@typeclient/react';
@Component()
class uxx implements ComponentTransform {
render(props: any) {
const { Consumer } = useSlot(props.ctx);
return
title
"foo" />
{props.children}
}
}
复制代码
import { inject } from 'inversify';
import { Route, Controller } from '@typeclient/core';
import { useSlot } from '@typeclient/react';
import { uxx } from './template.tsx';
@Controller()
@Template(uxx)
class router {
@inject(ttt) private readonly ttt: ttt;
@Route('/test')
test() {
const { Provider } = useSlot(props.ctx);
return
child ...
"foo">
this is foo slot
}
}
复制代码
title
this is foo slot
child ...
复制代码
解构项目的原则
IOCComponent + IOCService IOCMiddleware + IOCService IOCMiddlewware IOCService
通用化 内聚合 易扩展
通用化
内聚性
// service.ts
import { Service } from '@typeclient/core';
@Service()
export class NavService {
getTeams() {
// ... 这里可以是ajax请求的结果
return [
{
name: 'Team 1',
id: 1,
},
{
name: 'Team 2',
id: 1,
}
]
}
goTeam(id: number) {
// ...
console.log(id);
}
}
复制代码
// component.ts
import React, { useEffect, setState } from 'react';
import { Component, ComponentTransform } from '@typeclient/react';
import { NavService } from './service';
@Component()
export class NavBar implements ComponentTransform {
@inject(NavService) private readonly NavService: NavService;
render() {
const [teams, setTeams] = setState'getTeams']>>([]);
useEffect(() => this.NavService.getTeams().then(data => setTeams(data)), []);
return
{
teams.map(team =>- this.NavService.goTeam(team.id)}>{team.name}
)
}
}
}
复制代码
// @fe/navbar/index.ts
export * from './component';
复制代码
import React from 'react';
import { Component, ComponentTransform, useComponent } from '@typeclient/react';
import { NavBar } from '@fe/navbar';
@Component()
export class DEMO implements ComponentTransform {
@inject(NavBar) private readonly NavBar: NavBar;
render() {
const NavBar = useComponent(this.NavBar);
return
}
}
复制代码
易扩展
演示
框架: github.com/flowxjs/Typ… 项目模板: github.com/flowxjs/Typ… 简单的最佳实践: github.com/flowxjs/Typ…