How to use AOP + IOC to deconstruct the front-end project development

evio 2020-11-08 23:46:44
use aop ioc deconstruct front-end


This article will go through TypeClient Architecture to illustrate how to use AOP+IOC Ideas to deconstruct the development of front-end projects .

First statement ,AOP+IOC The understanding of ideas needs to have a certain foundation of programming architecture . at present , The scenarios in which these two ideas are used , Basically all in nodejs End , There is very little practice in the front end . I have the idea of providing a new way to deconstruct projects , Instead of overthrowing the community's huge family barrel . Just look at it , If it can give you better inspiration , Well, it's better , Welcome to exchange .

Now we will use TypeClient Of React For example, the rendering engine .

AOP

An idea of Aspect Oriented Programming . Its performance in the front end is the decorator of the front end , We can use decorators to intercept custom behavior before and after function execution .

AOP Its main function is to extract some functions unrelated to the core business logic module , These functions that are not related to business logic usually include log statistics 、 safety control 、 Exception handling . After taking out these functions , Re pass “ Dynamic weaving ” The business logic module . AOP First of all, it can keep the business logic module pure and highly cohesive , Secondly, it is easy to reuse log statistics and other functional modules .

The above is about the Internet AOP A simple explanation of . So the actual code might look like this

@Controller()
class Demo {
@Route() Page() {}
}
Copy code 

But a lot of times , We're just going to put some class The function under is just an object to store data , When you are sure to run this function, take out the data to do custom processing . Can pass reflect-metadata To learn more about the role of decorators .

IOC

Angular It is difficult to be accepted at home, in large part because its concept is too large , And one of the DI(dependency inject) It's even more confusing in use . Except DI There is also the idea of dependency injection called IOC. Its representative library is inversify. It's in github Owned on 6.7K Of star Count , In the community of dependency injection , Good reputation . We can learn about the benefits of this library for project deconstruction .

Examples are as follows :

@injectable()
class Demo {
@inject(Service) private readonly service: Service;
getCount() {
return 1 + this.service.sum(2, 3);
}
}
Copy code 
Of course ,Service Has been injected into inversify Of container Inside , To get through TypeClient This call .

Reorganize the front-end project runtime

In a general way , The front-end project will go through this process .

  1. By monitoring hashchange perhaps popstate Event intercepts browser behavior .
  2. Set the currently obtained window.location How data is mapped to a component .
  3. How components render to the page .
  4. When browser URL When it changes again , How do we map to a component and render .

This is a common solution for the community . Of course , We're not going to talk about how to design this pattern . We're going to deconstruct this process with a new design pattern .

Re examine the server-side routing system

We're talking about the architecture of the front end , Why do you talk about the architecture of the server ?

That's because , In fact, design patterns are not limited to the back end or the front end , It should be a more general way to solve specific problems .

So maybe someone will ask , The routing system of the server is not consistent with the front end , What is the significance? ?

We use nodejs Of http Module as an example , In fact, it's a bit similar to the front end .http Modules run in a process , adopt http.createServer In response to the data . We can argue that , The front page is equivalent to a process , We respond by listening to events in the corresponding mode to get the component rendered to the page .

Multiple servers Client Send a request to server End port processing , Why can't we use the analogy of front-end users operating the browser address bar to get the response entry through events ?

The answer is yes . We call this way virtual server That is, virtual services based on page level .

Since we can abstract a service architecture , Of course , We can be exactly like nodejs The service-oriented solution of the project is close to , We can handle the front-end routing as follows nodejs End common way , More in line with our intentions and abstractions .

history.route('/abc/:id(d+)', (ctx) => {
const id = ctx.params.id;
return <div>{id}</div>;
// perhaps : ctx.body = <div>{id}</div>; This is more understandable
})
Copy code 

Modifying routing design

If it's written in the above way , So it can also solve the basic problem , But it doesn't fit us AOP+IOC The design of the , It's rather tedious when writing , And it doesn't deconstruct the response logic .

We need to solve the following problems :

  1. How to parse routing string rules ?
  2. This rule is used to quickly match callbacks ?

There are many libraries for parsing routing rules on the server side , What is more representative is path-to-regexp, It is used in KOA In the famous architecture . Its principle is to regularize strings , Use the currently passed in path To match the corresponding rules to get the corresponding callback function to deal with . But there are some flaws in this approach , That is, regular matching is slower , When the last rule of the processing queue is matched , All the rules will be enforced , When there are too many routes, the performance is poor , This can be seen from what I wrote earlier koa-rapid-router transcend koa-router The performance of the 100 Many times . Another flaw is , It's matched in the order you write it , So it has a certain order , Developers need to pay great attention to . such as :

http.get('/:id(d+)', () => console.log(1));
http.get('/1234', () => console.log(2));
Copy code 

If we visit /1234, So it will print out 1, Instead of 2.

In order to solve the performance and optimize the intelligence of the matching process , We can refer to find-my-way The routing design architecture of . Please see for yourself , I don't parse . All in all , It's a string indexing algorithm , It can match the route we need quickly and intelligently . The famous fastify This architecture is used to achieve high performance .

TypeClient The routing design of

We can quickly define our route through some simple decorators , The essence is to adopt find-my-way Routing design principles of .

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<Context>) {
const status = useReactiveState(() => props.status.value);
return <div>Hello world! {status}</div>;
}
}
// --------------------------
// stay index.ts As long as
app.setController(DemoController);
// It automatically binds the route , At the same time, the page enters the route `/api/test` When
// The text will be displayed `Hello world! 200`.
Copy code 
so ,TypeClient adopt AOP The idea of defining routing is very simple .

Routing lifecycle

When you jump from one page to another , The life cycle of the previous page ends , therefore , Routing has a lifecycle . Again , We break down the entire page cycle as follows :

  1. beforeCreate The page starts loading
  2. created Page loading complete
  3. beforeDestroy The page is about to be destroyed
  4. destroyed The page has been destroyed

To show this 4 Life cycles , We according to the React Of hooks A special function useContextEffect To deal with the side effects of the routing lifecycle . such as :

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<Context>) {
const status = useReactiveState(() => props.status.value);
useContextEffect(() => {
console.log(' The route load is complete ');
return () => console.log(' The route is destroyed ');
})
return <div>Hello world! {status}</div>;
}
}
Copy code 

In fact, it is related to useEffect perhaps useLayoutEffect Some similar . It's just that we focus on the life cycle of routing , and react Focus on the lifecycle of the component .

In fact, through the above props.status.value We can guess , Routing is stateful , Namely 100 and 200 also 500 wait . We can use such data to determine what life cycle the current route is in , You can also render different effects through the skeleton screen .

Middleware design

In order to control the operation of the routing lifecycle , We designed the middleware pattern , It is used to handle the behavior before routing , For example, request data and so on . In principle, middleware adopts and KOA Consistent patterns , This can be very compatible with the community ecology .

const middleware = async (ctx, next) => {
// ctx.....
await next();
}
Copy code 

adopt AOP We can easily reference this middleware , Data processing before page loading finished state .

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<Context>) {
const status = useReactiveState(() => props.status.value);
useContextEffect(() => {
console.log(' The route load is complete ');
return () => console.log(' The route is destroyed ');
})
return <div>Hello world! {status}</div>;
}
}
Copy code 

Design cycle state management - ContextStore

It has to be said that this is a bright spot . Why design such a pattern ? It is mainly to solve the problem that the operation of data in the process of middleware can respond to the page in time . Because middleware implements and react Page rendering is synchronous , So we design this pattern to facilitate the periodization of data .

We used a very black tech solution to this problem :@vue/reactity

Yes , Is it .

We are react Embedded in VUE3 The latest responsive system , Let's develop fast update data , And give up dispatch The process . Of course , This is very powerful for middleware to update data .

here I thank you very much sl1673495 Given the black technology ideas, our design can be perfectly compatible react.

We go through @State(callback) To define ContextStore Initialization data of , adopt useContextState perhaps useReactiveState Track data changes and respond to React On the page .

Let's look at an example :

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<Context>) {
const status = useReactiveState(() => props.status.value);
const count = useReactiveState(() => props.state.count);
const click = useCallback(() => ctx.state.count++, [ctx.state.count]);
useContextEffect(() => {
console.log(' The route load is complete ');
return () => console.log(' The route is destroyed ');
})
return <div onClick={click}>Hello world! {status} - {count}</div>;
}
}
function createState() {
return {
count: 0,
}
}
Copy code 

You can see the constant click , The data is changing . This way of operation greatly simplifies the writing of our data , At the same time, it can also be associated with vue3 Responsive ability is in line with , make up react The short board of data operation complexity .

In addition to using this black technology in cycles , In fact, it can also be used independently , For example, define... Anywhere :

// test.ts
import { reactive } from '@vue/reactity';
export const data = reactive({
count: 0,
})
Copy code 

We can use... In any component

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 <div onClick={onClick}>{count}</div>
}
Copy code 

utilize IOC Thought deconstruction project

None of the above explanations are designed IOC aspect , So the following will explain IOC Use .

Controller Service deconstruction

Let's write a Service file

import { Service } from '@typeclient/core';
@Service()
export class MathService {
sum(a: number, b: number) {
return a + b;
}
}
Copy code 

And then we can do it before Controller Call directly :

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<Context>) {
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(' The route load is complete ');
return () => console.log(' The route is destroyed ');
})
return <div onClick={click}>Hello world! {status} + {count} = {value}</div>;
}
}
function createState() {
return {
count: 0,
}
}
Copy code 

You can see the data changing .

Component deconstruction

We are react A new component pattern is created by the component of , call IOCComponent. It's a way to have IOC The components of capability , We go through useComponent Of hooks To call .

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 <div>{value}</div>
}
}
Copy code 

It is then invoked in any component

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<Context>) {
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(' The route load is complete ');
return () => console.log(' The route is destroyed ');
})
return <div onClick={click}>
Hello world! {status} + {count} = {value}
<Demo a={count} b={value} />
</div>;
}
}
function createState() {
return {
count: 0,
}
}
Copy code 

Middleware deconstruction

We can abandon the traditional middleware writing method , The middleware can be added and deconstructed :

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();
}
}
Copy code 

by react newly added Slot Slot concept

It supports Slot Slot mode , We can go through useSlot get Provider And Consumer. It's a pattern of passing node fragments through messages .

const { Provider, Consumer } = useSlot(ctx.app);
<Provider name="foo">provider data</Provider>
<Consumer name="foo">placeholder</Consumer>
Copy code 

And then write a IOCComponent Or traditional components .

// template.tsx
import { useSlot } from '@typeclient/react';
@Component()
class uxx implements ComponentTransform {
render(props: any) {
const { Consumer } = useSlot(props.ctx);
return <div>
<h2>title</h2>
<Consumer name="foo" />
{props.children}
</div>
}
}
Copy code 

Last in Controller On the call

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 <div>
child ...
<Provider name="foo">
this is foo slot
</Provider>
</div>
}
}
Copy code 

The structure you can see is as follows :

<div>
<h2>title</h2>
this is foo slot
<div>child ...</div>
</div>
Copy code 

Principles for deconstructing projects

We can pass the IOC Services and Middleware There are also components that deconstruct at different latitudes , Packaged as a unified npm The package is uploaded to the private warehouse for internal development .

type

  1. IOCComponent + IOCService
  2. IOCMiddleware + IOCService
  3. IOCMiddlewware
  4. IOCService

principle

  1. Generalization
  2. Internal polymerization
  3. Easy to expand

Following this principle can make the company's business code or components highly reusable , And through AOP It can clearly and intuitively show the charm of code or document .

Generalization

That is to guarantee the logic encapsulated 、 High degree of generality of code or component , There is no need to encapsulate for less general purpose . for instance , Unified navigation head within the company , Navigation head may be used in any project for unification , So it is very suitable to package as a component module .

Cohesion

Common components need unified data , So you can go through IOCComponent + IOCService + IOCMiddleware In the form of , In the appropriate use, just focus on importing this component . Or for example, the general navigation head . For example, the navigation header needs to drop down a list of teams , that , We can define this component like this :

One service file :

// service.ts
import { Service } from '@typeclient/core';
@Service()
export class NavService {
getTeams() {
// ... This could be ajax Requested results
return [
{
name: 'Team 1',
id: 1,
},
{
name: 'Team 2',
id: 1,
}
]
}
goTeam(id: number) {
// ...
console.log(id);
}
}
Copy code 

Components :

// 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<ReturnType<NavService['getTeams']>>([]);
useEffect(() => this.NavService.getTeams().then(data => setTeams(data)), []);
return <ul>
{
teams.map(team => <li onClick={() => this.NavService.goTeam(team.id)}>{team.name}</li>)
}
</ul>
}
}
Copy code 

We define this module as @fe/navbar, Export this object at the same time :

// @fe/navbar/index.ts
export * from './component';
Copy code 

At random IOC Components can be called in this way

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 <NavBar />
}
}
Copy code 

You can see that just load this component , The request data is automatically loaded , This is very different from the normal component pattern , It can be a business type component deconstruction solution . Very practical .

Easy to expand

The main thing is to keep extensibility when designing this generic code or component , for instance , Skillfully use SLOT Slot principle , We can reserve some space for slots , It is convenient for this component to be transmitted by using different location code and replace the original location content , The benefits of this need to be realized by developers themselves .

demonstration

We provided one demo To show its ability , And you can see from the code how to deconstruct the entire project . Every one of us Controller Can exist independently , Make project content migration very easy .

You can learn about the development mode through the above two examples .

summary

The new concept of development is not to get rid of traditional development methods and communities , And offer better ideas . Of course , The good and bad of this kind of thinking , Each has its own understanding . But I still want to state , I'm just offering a new idea today , Just look at it , Give me what I like star. Thank you very much !

版权声明
本文为[evio]所创,转载请带上原文链接,感谢

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云