如何利用AOP+IOC思想解構(gòu)前端項目開發(fā)
作者:evio
來源:SegmentFault 思否社區(qū)
本文將通過 TypeClient 架構(gòu)來闡述如何利用AOP+IOC思想來解構(gòu)前端項目的開發(fā)。
首先聲明,AOP+IOC思想的理解需要有一定的編程架構(gòu)基礎(chǔ)。目前,這兩大思想使用的場景,基本都在nodejs端,在前端的實踐非常少。我本著提供一種新的項目解構(gòu)思路的想法,而非推翻社區(qū)龐大的全家桶。大家看看就好,如果能給你提供更好的靈感,那么再好不過了,非常歡迎交流。
以下我們將以 TypeClient 的 React 渲染引擎為例。
AOP
一種面向切面編程的思想。它在前端的表現(xiàn)是前端的裝飾器,我們可以通過裝飾器來攔截函數(shù)執(zhí)行前與執(zhí)行后的自定義行為。
AOP的主要作用是把一些跟核心業(yè)務(wù)邏輯模塊無關(guān)的功能抽離出來,這些跟業(yè)務(wù)邏輯無關(guān)的功能通常包括日志統(tǒng)計、安全控制、異常處理等。把這些功能抽離出來之后, 再通過“動態(tài)織入”的方式摻入業(yè)務(wù)邏輯模塊中。AOP的好處首先是可以保持業(yè)務(wù)邏輯模塊的純凈和高內(nèi)聚性,其次是可以很方便地復(fù)用日志統(tǒng)計等功能模塊。
以上是網(wǎng)絡(luò)上對AOP的簡單解釋。那么實際代碼也許是這樣的
@Controller()
class?Demo?{
??@Route()?Page()?{}
}
復(fù)制代碼
但很多時候,我們僅僅是將某個class下的函數(shù)當(dāng)作一個儲存數(shù)據(jù)的對象而已,而在確定運行這個函數(shù)時候拿出數(shù)據(jù)做自定義處理??梢酝ㄟ^ reflect-metadata 來了解更多裝飾器的作用。
IOC
Angular難以被國內(nèi)接受很大一部分原因是它的理念太龐大,而其中的DI(dependency inject)在使用時候則更加讓人迷糊。其實除了DI還有一種依賴注入的思想叫 IOC。它的代表庫為 inversify。它在github上擁有6.7K的star數(shù),在依賴注入的社區(qū)里,口碑非常好。我們可以先通過這個庫來了解下它對項目解構(gòu)的好處。
例子如下:
@injectable()
class?Demo?{
??@inject(Service)?private?readonly?service:?Service;
??getCount()?{
????return?1?+?this.service.sum(2,?3);
??}
}
復(fù)制代碼
當(dāng)然,Service已經(jīng)優(yōu)先被注入到inversify的container內(nèi)了,才可以通過 TypeClient 這樣調(diào)用。
重新梳理前端項目運行時
一般地,前端項目會經(jīng)過這樣的運行過程。
通過監(jiān)聽hashchange或者popstate事件攔截瀏覽器行為。 設(shè)定當(dāng)前獲得的window.location 數(shù)據(jù)如何對應(yīng)到一個組件。 組件如何渲染到頁面。 當(dāng)瀏覽器URL再次變化的時候,我們?nèi)绾螌?yīng)到一個組件并且渲染。
重新審視服務(wù)端路由體系
改造路由設(shè)計
如何解析路由字符串規(guī)則? 如何利用這個規(guī)則快速匹配到對應(yīng)的回調(diào)函數(shù)?
http.get('/:id(d+)',?()?=>?console.log(1));
http.get('/1234',?()?=>?console.log(2));
復(fù)制代碼
TypeClient 的路由設(shè)計
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);
????return?Hello?world!?{status};
??}
}
//?--------------------------
//?在index.ts中只要
app.setController(DemoController);
//?它就自動綁定了路由,同時頁面進入路由?`/api/test`?的時候
//?就會顯示文本?`Hello world! 200`。
復(fù)制代碼
路由生命周期
beforeCreate 頁面開始加載 created 頁面加載完成 beforeDestroy 頁面即將銷毀 destroyed 頁面已經(jīng)銷毀
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('路由被銷毀了');
????})
????return?Hello?world!?{status};
??}
}
復(fù)制代碼
中間件設(shè)計
const?middleware?=?async?(ctx,?next)?=>?{
??//?ctx.....
??await?next();
}
復(fù)制代碼
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('路由被銷毀了');
????})
????return?Hello?world!?{status};
??}
}
復(fù)制代碼
設(shè)計周期狀態(tài)管理 - 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('路由被銷毀了');
????})
????return?Hello?world!?{status}?-?{count};
??}
}
function?createState()?{
??return?{
????count:?0,
??}
}
復(fù)制代碼
//?test.ts
import?{?reactive?}?from?'@vue/reactity';
export?const?data?=?reactive({
??count:?0,
})
復(fù)制代碼
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}
}
復(fù)制代碼
利用IOC思想解構(gòu)項目
Controller 服務(wù)解構(gòu)
import?{?Service?}?from?'@typeclient/core';
@Service()
export?class?MathService?{
??sum(a:?number,?b:?number)?{
????return?a?+?b;
??}
}
復(fù)制代碼
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('路由被銷毀了');
????})
????return?Hello?world!?{status}?+?{count}?=?{value};
??}
}
function?createState()?{
??return?{
????count:?0,
??}
}
復(fù)制代碼
Component 解構(gòu)
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}
??}
}
復(fù)制代碼
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,
??}
}
復(fù)制代碼
Middleware 解構(gòu)
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();
??}
}
復(fù)制代碼
為react新增Slot插槽概念
const?{?Provider,?Consumer?}?=?useSlot(ctx.app);"foo">provider?data "foo">placeholder
復(fù)制代碼
//?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}
????
??}
}
復(fù)制代碼
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
??????
????
??}
}
復(fù)制代碼
??title
??this?is?foo?slot
??child?...
復(fù)制代碼
解構(gòu)項目的原則
IOCComponent + IOCService IOCMiddleware + IOCService IOCMiddlewware IOCService
通用化 內(nèi)聚合 易擴展
通用化
內(nèi)聚性
//?service.ts
import?{?Service?}?from?'@typeclient/core';
@Service()
export?class?NavService?{
??getTeams()?{
????//?...?這里可以是ajax請求的結(jié)果
????return?[
??????{
????????name:?'Team?1',
????????id:?1,
??????},
??????{
????????name:?'Team?2',
????????id:?1,
??????}
????]
??}
??goTeam(id:?number)?{
????//?...
????console.log(id);
??}
}
復(fù)制代碼
//?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}
)
??????}
????
??}
}
復(fù)制代碼
//?@fe/navbar/index.ts
export?*?from?'./component';
復(fù)制代碼
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?
??}
}
復(fù)制代碼
易擴展
演示
框架:?github.com/flowxjs/Typ… 項目模板:?github.com/flowxjs/Typ… 簡單的最佳實踐:?github.com/flowxjs/Typ…
總結(jié)

