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. [front end -- JavaScript] knowledge point (IV) -- memory leakage in the project (I)
  2. This mechanism in JS
  3. Vue 3.0 source code learning 1 --- rendering process of components
  4. Learning the realization of canvas and simple drawing
  5. gin里获取http请求过来的参数
  6. vue3的新特性
  7. Get the parameters from HTTP request in gin
  8. New features of vue3
  9. vue-cli 引入腾讯地图(最新 api,rocketmq原理面试
  10. Vue 学习笔记(3,免费Java高级工程师学习资源
  11. Vue 学习笔记(2,Java编程视频教程
  12. Vue cli introduces Tencent maps (the latest API, rocketmq)
  13. Vue learning notes (3, free Java senior engineer learning resources)
  14. Vue learning notes (2, Java programming video tutorial)
  15. 【Vue】—props属性
  16. 【Vue】—创建组件
  17. [Vue] - props attribute
  18. [Vue] - create component
  19. 浅谈vue响应式原理及发布订阅模式和观察者模式
  20. On Vue responsive principle, publish subscribe mode and observer mode
  21. 浅谈vue响应式原理及发布订阅模式和观察者模式
  22. On Vue responsive principle, publish subscribe mode and observer mode
  23. Xiaobai can understand it. It only takes 4 steps to solve the problem of Vue keep alive cache component
  24. Publish, subscribe and observer of design patterns
  25. Summary of common content added in ES6 + (II)
  26. No.8 Vue element admin learning (III) vuex learning and login method analysis
  27. Write a mini webpack project construction tool
  28. Shopping cart (front-end static page preparation)
  29. Introduction to the fluent platform
  30. Webpack5 cache
  31. The difference between drop-down box select option and datalist
  32. CSS review (III)
  33. Node.js学习笔记【七】
  34. Node.js learning notes [VII]
  35. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  36. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  37. 【JQuery框架,Java编程教程视频下载
  38. [jQuery framework, Java programming tutorial video download
  39. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  40. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  41. 【Vue,阿里P8大佬亲自教你
  42. 【Vue基础知识总结 5,字节跳动算法工程师面试经验
  43. [Vue, Ali P8 teaches you personally
  44. [Vue basic knowledge summary 5. Interview experience of byte beating Algorithm Engineer
  45. 【问题记录】- 谷歌浏览器 Html生成PDF
  46. [problem record] - PDF generated by Google browser HTML
  47. 【问题记录】- 谷歌浏览器 Html生成PDF
  48. [problem record] - PDF generated by Google browser HTML
  49. 【JavaScript】查漏补缺 —数组中reduce()方法
  50. [JavaScript] leak checking and defect filling - reduce() method in array
  51. 【重识 HTML (3),350道Java面试真题分享
  52. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  53. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  54. [re recognize HTML (3) and share 350 real Java interview questions
  55. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  56. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  57. 【重识 HTML ,nginx面试题阿里
  58. 【重识 HTML (4),ELK原来这么简单
  59. [re recognize HTML, nginx interview questions]
  60. [re recognize HTML (4). Elk is so simple