Как использовать идею AOP+IOC для деконструкции фронтенд-проекта

React.js

Эта статья пройдетTypeClientАрхитектура, чтобы проиллюстрировать, как использовать идею AOP+IOC для деконструкции разработки интерфейсных проектов.

Front-end разработка будет постепенно стремиться к разработке IOC в теории

Во-первых, утверждается, что для понимания идеи АОП+ИОК требуется определенная основа программирования. В настоящее время сценарии, используемые этими двумя основными идеями, в основном находятся на стороне nodejs, и очень мало практики во внешнем интерфейсе. Вместо того, чтобы ниспровергнуть огромное семейное ведро сообщества, я в духе предоставления нового способа мышления о проекте. Это хорошо для всех, чтобы взглянуть. Если это может дать вам больше вдохновения, было бы здорово. Общение очень приветствуется.

Ниже мы будем использовать движок рендеринга React TypeClient в качестве примера.

AOP

Идея аспектно-ориентированного программирования. Его интерфейсная производительность — это внешний декоратор, который можно использовать для перехвата пользовательского поведения до и после выполнения функции.

Основная функция АОП состоит в извлечении некоторых функций, не связанных с основным модулем бизнес-логики.Эти функции, не связанные с бизнес-логикой, обычно включают статистику журналов, контроль безопасности и обработку исключений. После того, как эти функции извлечены, они затем включаются в модуль бизнес-логики посредством «динамического переплетения». Преимущество АОП заключается в том, что он может поддерживать чистоту и высокую связность модулей бизнес-логики, а во-вторых, он может легко повторно использовать функциональные модули, такие как статистика журналов.

Выше приведено простое объяснение АОП в Интернете. Тогда фактический код может быть примерно таким

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

Но во многих случаях мы просто рассматриваем функцию определенного класса как объект, в котором хранятся данные, и когда мы решаем запустить эту функцию, мы извлекаем данные для пользовательской обработки. в состоянии пройтиreflect-metadataчтобы узнать больше о том, что делают декораторы.

IOC

Большая часть причин, по которым Angular трудно принять в Китае, заключается в том, что его концепция слишком велика, а DI (внедрение зависимостей) в нем еще больше сбивает с толку при использовании. На самом деле помимо DI есть еще одна идея, называемая IOC. Его репрезентативная библиотекаinversify. Он имеет 6,7 тыс. звезд на github и имеет очень хорошую репутацию в сообществе внедрения зависимостей. Мы можем сначала использовать эту библиотеку, чтобы понять ее преимущества для деконструкции проекта.

Примеры следующие:

@injectable()
class Demo {
  @inject(Service) private readonly service: Service;
  getCount() {
    return 1 + this.service.sum(2, 3);
  }
}

Конечно, служба сначала внедряется в контейнер inversify, прежде чем ее можно будет вызвать через TypeClient.

Реорганизовать интерфейсную среду выполнения проекта

Как правило, интерфейсные проекты проходят через такой запущенный процесс.

  1. слушаяhashchangeилиpopstateСобытия перехватывают поведение браузера.
  2. Установить текущее полученноеwindow.locationКак данные соответствуют компоненту.
  3. Как компонент отображается на странице.
  4. Когда URL-адрес браузера снова изменится, как нам сопоставить компонент и отобразить его.

Это обычное решение для сообщества. Конечно, мы не будем больше объяснять, как спроектировать этот узор. Мы разберем этот процесс с помощью нового шаблона проектирования.

Пересмотрите систему маршрутизации на стороне сервера.

Мы говорим о интерфейсной архитектуре, почему мы говорим о серверной архитектуре?

Это потому, что на самом деле шаблон проектирования не ограничивается бэкендом или интерфейсом, он должен быть более общим способом решения конкретных задач.

Поэтому некоторые люди могут спросить, система маршрутизации сервера не согласуется с внешним интерфейсом, в чем смысл?

Возьмем в качестве примера http-модуль nodejs, на самом деле он чем-то похож на интерфейс. Модуль http запускается в процессе черезhttp.createServerФункция обратного вызова параметра для ответа данными. Мы можем думать, что фронтенд-страница эквивалентна процессу, и мы реагируем на рендеринг компонента на страницу, прослушивая события в соответствующем режиме.

Несколько клиентов на сервере отправляют запросы на порт сервера для обработки.Почему это не может быть аналогично интерфейсному пользователю, работающему с адресной строкой браузера, для получения записи ответа через события?

Ответ положительный. Мы называем этот путьvirtual serverТо есть на основе виртуальных служб уровня страницы.

Поскольку его можно абстрагировать как сервисную архитектуру, конечно, мы можем приблизиться к сервис-ориентированному решению nodejs, и мы можем обычным образом обрабатывать интерфейсную маршрутизацию на стороне nodejs, что больше соответствует наше намерение и абстракция.

history.route('/abc/:id(\\d+)', (ctx) => {
  const id = ctx.params.id;
  return <div>{id}</div>;
  // 或者: ctx.body = <div>{id}</div>; 这种更加能理解
})

Модернизация маршрутизации

Если это описанный выше метод записи, он также может решить основную проблему, но он не соответствует нашему дизайну AOP+IOC, его все еще относительно громоздко писать, и он не деконструирует логику ответа.

Нам необходимо решить следующие задачи:

  1. Как разобрать правила строки маршрута?
  2. Как использовать это правило для быстрого сопоставления соответствующей функции обратного вызова?

Существует множество библиотек для разбора правил маршрутизации на стороне сервера, которые представляютpath-to-regexp, который используется вKOAи другие известные архитектуры. Его принцип заключается в упорядочивании строки, использовании текущего входящего пути для соответствия соответствующим правилам и получении соответствующей функции обратного вызова для обработки. Однако у этого подхода есть некоторые недостатки, а именно: обычная скорость сопоставления медленная.При совпадении последнего правила в очереди обработки будут выполняться все правила.Когда маршрутов слишком много, производительность низкая.Вы можете обратитесь ко мне раньше.koa-rapid-router превосходит koa-router более чем в 100 раз. Еще один недостаток заключается в том, что его метод сопоставления сопоставляется в соответствии с порядком, который вы пишете, поэтому он имеет определенный порядок, и разработчикам следует уделять ему большое внимание. Например:

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

если мы посетим/1234, то он будет печатать1, вместо2.

Чтобы повысить производительность и оптимизировать интеллект процесса сопоставления, мы можем обратиться кfind-my-wayсистема проектирования маршрутов. Пожалуйста, обратитесь к официальному для деталей, я не буду анализировать это. Короче говоря, это алгоритм индексации строк, который может быстро и разумно сопоставить нужный нам маршрут. известныйfastifyЭта архитектура используется для достижения высокой производительности.

Схема маршрутизации TypeClient

Мы можем быстро определить наши маршруты через несколько простых декораторов, суть в том, чтобы использоватьfind-my-wayпринципы проектирования маршрутизации.

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>;
  }
}
// --------------------------
// 在index.ts中只要
app.setController(DemoController);
// 它就自动绑定了路由,同时页面进入路由 `/api/test` 的时候
// 就会显示文本 `Hello world! 200`。

Видно, что для TypeClient очень просто определить маршруты с помощью концепции АОП.

жизненный цикл маршрутизации

При переходе с одной страницы на другую жизненный цикл предыдущей страницы немедленно заканчивается, поэтому маршрут имеет жизненный цикл. Затем мы разбираем весь цикл страницы следующим образом:

  1. Страница BeForecreate начинает загружать
  2. создал страницу нагрузки
  3. Страница beforeDestroy будет уничтожена
  4. уничтоженная страница была уничтожена

Чтобы представить эти 4 жизненных цикла, мы специально сделали функцию на основе хуков React.useContextEffectдля обработки побочных эффектов жизненного цикла маршрутизации. Например:

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('路由加载完成了');
      return () => console.log('路由被销毁了');
    })
    return <div>Hello world! {status}</div>;
  }
}

На самом деле это связано сuseEffectилиuseLayoutEffectНесколько похоже. Но мы обеспокоены тем, что маршрут жизненного цикла, и реагировать на жизненный цикл компонента внимания.

На самом деле, через вышеизложенноеprops.status.valueМы можем предположить, что маршруты являются записями с сохранением состояния, т.е.100а также200а также500и т.п. Мы можем использовать такие данные, чтобы определить, в каком жизненном цикле находится текущий маршрут, а также мы можем визуализировать различные эффекты через скелетный экран.

Дизайн промежуточного программного обеспечения

Чтобы контролировать работу жизненного цикла маршрутизации, мы разработали режим промежуточного программного обеспечения для управления поведением предварительной обработки маршрутизации, например, запросом данных и т. д. В принципе, промежуточное ПО использует тот же режим, что и KOA, что может быть в значительной степени совместимо с экологией сообщества.

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<Context>) {
    const status = useReactiveState(() => props.status.value);
    useContextEffect(() => {
      console.log('路由加载完成了');
      return () => console.log('路由被销毁了');
    })
    return <div>Hello world! {status}</div>;
  }
}

Управление состоянием цикла проектирования — ContextStore

Я должен сказать, что это изюминка. Зачем создавать такой узор? Основная цель - решить проблему, что операция данных в процессе промежуточного программного обеспечения может вовремя реагировать на страницу. Поскольку выполнение промежуточного ПО синхронизировано с рендерингом реагирующей страницы, мы разработали такой режим для обеспечения периодичности данных.

Мы приняли очень сложное решение для решения этой проблемы:@vue/reactity

Да это оно.

Встраиваем в реакциюVUE3Новейшая адаптивная система позволяет нам быстро обновлять данные, не теряяdispatchОбработать. Конечно, для промежуточного программного обеспечения это чрезвычайно полезно для обновления данных.

вот я очень благодаренsl1673495Приведенные идеи черной технологии делают наш дизайн идеально совместимым с реакцией.

мы проходим@State(callback)Чтобы определить данные инициализации ContextStore, черезuseContextStateилиuseReactiveStateОтслеживайте изменения данных и отвечайте на страницы React.

Давайте посмотрим пример:

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('路由加载完成了');
      return () => console.log('路由被销毁了');
    })
    return <div onClick={click}>Hello world! {status} - {count}</div>;
  }
}

function createState() {
  return {
    count: 0,
  }
}

Вы можете увидеть постоянные клики и данные постоянно меняются. Этот метод эксплуатации значительно упрощает метод написания наших изменений наших данных, и в то же время он может быть выровнен с отзывчивыми возможностями Vue3, составляя недостатки сложности операции реагирования на работу.

Помимо использования этой черной технологии в цикле, ее также можно использовать независимо, например, определить ее в любом положении:

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

Деконструкция проектов с использованием идей IOC

Вышеприведенные пояснения не относятся к IOC, поэтому ниже будет объяснено использование IOC.

Деконструкция службы контроллера

Давайте сначала напишем служебный файл

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<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('路由加载完成了');
      return () => console.log('路由被销毁了');
    })
    return <div onClick={click}>Hello world! {status} + {count} = {value}</div>;
  }
}

function createState() {
  return {
    count: 0,
  }
}

Вы можете видеть, что данные постоянно меняются.

Деструктуризация компонентов

Мы создали новый шаблон компонента для реагирующих компонентов под названиемIOCComponent. Это компонент с поддержкой IOC, мы передаемuseComponentкрючки для вызова.

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>
  }
}

а затем вызвать любой компонент

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('路由加载完成了');
      return () => console.log('路由被销毁了');
    })
    return <div onClick={click}>
      Hello world! {status} + {count} = {value} 
      <Demo a={count} b={value} />
    </div>;
  }
}

function createState() {
  return {
    count: 0,
  }
}

Деконструкция промежуточного программного обеспечения

Мы можем полностью отказаться от традиционного метода написания промежуточного программного обеспечения и использовать метод написания промежуточного программного обеспечения, который можно деконструировать:

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

Добавлена ​​концепция слота Slot, чтобы реагировать

Он поддерживает режим слота Slot, мы можем получить Provider и Consumer через useSlot. Это шаблон передачи фрагментов узлов через сообщения.

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

Затем напишите IOCComponent или традиционный компонент.

// 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>
  }
}

Наконец, вызовите диспетчера

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>
  }
}

Структура, которую вы можете увидеть, выглядит следующим образом:

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

Принципы деконструкции проектов

Мы можем деконструировать IOC-сервис, Middleware и компоненты на разных широтах, упаковать их в единый npm-пакет и загрузить его на личный склад для внутренней разработки и использования в компании.

Типы

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

в общем

  1. Универсальный
  2. Внутренняя полимеризация
  3. Легко расширить

Следование этому принципу может сделать бизнес-код или компоненты компании пригодными для повторного использования, а АОП может ясно и интуитивно выразить прелесть кода в виде документации.

Универсальный

Это делается для того, чтобы инкапсулированная логика, код или компоненты были в высшей степени обобщенными, и нет необходимости инкапсулировать менее общие. Например, унифицированная навигационная головка внутри компании, навигационная головка может использоваться в любом проекте для унификации, поэтому ее очень удобно упаковывать в виде составного модуля.

сплоченность

Общие компоненты нуждаются в получении унифицированных данных, поэтому их можно упаковать в виде IOCComponent + IOCService + IOCMiddleware, и вам нужно только обратить внимание на импорт этого компонента, когда он пригоден для использования. Или пример общего навигационного заголовка. Например, заголовок навигации должен раскрывать список команд, тогда мы можем определить этот компонент следующим образом:

Сервисный файл:

// 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<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>
  }
}

Мы определяем этот модуль как@fe/navbar, при экспорте этого объекта:

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

Это может быть вызвано так в любом компоненте IOC

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 />
  }
}

Вы можете обнаружить, что пока этот компонент загружается, данные запроса загружаются автоматически, что сильно отличается от обычного режима компонента, который может быть бизнес-ориентированной схемой деконструкции компонента. Очень практично.

Легко расширить

В основном для того, чтобы сохранить возможность расширения при разработке этого кода или компонента общего назначения.Например, умело используя принцип слота SLOT, мы можем зарезервировать некоторое пространство для слота, чтобы этот компонент можно было передавать кодами в разных местах.И заменить содержание оригинальной локации, преимущества этого должны испытать сами разработчики.

демо

Мы предоставляем демонстрацию, чтобы продемонстрировать его возможности, и вы можете увидеть из кода, как разобрать весь проект. Каждый из наших контроллеров может существовать независимо, что делает перенос содержимого проекта очень простым.

Вы можете понять режим разработки через два приведенных выше примера.

Суммировать

Новая концепция разработки заключается не в том, чтобы позволить вам отказаться от традиционных методов разработки и сообществ, а в том, чтобы предоставить лучшие идеи. Конечно, такое мышление хорошо и плохо, у каждого свое понимание. Но я все же хочу заявить, что сегодня я просто предлагаю новую идею, просто взгляните на нее и поставьте звезду, если она вам нравится. большое спасибо!