JSX AS DSL? Напишите сервер Mock API, чтобы увидеть

внешний интерфейс React.js
JSX AS DSL? Напишите сервер Mock API, чтобы увидеть

В эти дни я собираюсь написать простой API Mock сервер Это обычное дело, да? На самом деле, я хочу поговорить о JSX, мок-сервер — это просто прикрытие.

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


Схема статьи


1. Описание проблемы с доменом

Как только я упомянул такой абстрактный термин, что, черт возьми, такое «проблема домена»? Что такое домен, это очень хорошо объяснено на вики,Поле относится к охвату определенного профессионального или аспекта вещей.. Тогда под так называемыми предметными проблемами можно понимать требования, которые нам необходимо решить с помощью программ или других средств.

Например, когда речь идет о API Mock server, нам нужно решить проблемы сопоставления запросов и моделирования данных, Nginx решает проблему сервера ресурсов и прокси, HTML + CSS решает проблему отображения пользовательского интерфейса страницы...

Мы здесь, чтобы сосредоточиться на'описывать'.Эти описания представляют собой «внешний интерфейс» или пользовательский интерфейс (UI), предоставляемый экспертам в предметной области.. Например:



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


1.1 Форма файла конфигурации

JSON?

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

YAML?

По сравнению с синтаксисом JSON он намного проще и читабельнее. Отлично подходит в качестве профильной формы

Или какая-то другая форма файла конфигурации...

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

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

Конечно, вы можете заменить «функцию» другими способами, такими как шаблоны или поддержка сценариев.



1.2 Языки программирования и внутренние DSL

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

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

Я, наверное, говорюDSL(Domain-specific languages):

DSL — это компьютерный язык, используемый для описания конкретной области приложения. DSL имеет очень широкий спектр приложений в области компьютеров, таких как HTML для описания веб-страниц, язык запросов к базам данных SQL и регулярные выражения. Соответственноязык универсальных типов(GPL, General-Purpose Language), такие как Java, C++, JavaScript. Они могут использоваться для описания любой предметной логики, которая обычноПолнота по Тьюрингуиз. Подумайте об этом так, хотя и в общих чертах: любой язык, кроме языка с универсальной типизацией, является DSL.


Как создать DSL?

Разработать новый язык с нуля? Нет! Цена слишком высока.

Более элегантный способ — выполнять вычитание или инкапсулировать абстракции поверх языков программирования общего назначения. Конечно, не все типы языков имеют эту «возможность», например, Java и C/C++, их синтаксис слишком подробный или цепочка инструментов слишком тяжелая. Но такие языки, как Groovy, Ruby, Scala и Elixir, упрощают создание «DSL», и большинство из них являются динамическими языками.

Некоторые из них очень подходят в качестве DSL с помощью макросов, некоторые рождаются с помощью синтаксиса, а некоторые обладают очень сильными возможностями динамического программирования... Эти факторы делают их подходящими в качестве материнских (хост) DSL.

Мы также обычно называем этот DSL какEmbedded DSL(嵌入式 DSL)или内部 DSL, потому что они паразитируют в универсальном типизированном языке программирования. А автономные DSL, такие как JSON, HTML, называются外部DSL.

Преимущество внутреннего DSL заключается в том, что он упрощает реализацию языка (Parse->Transform->Generate).


Вот два очень типичных примера:

Обычно используется разработчиками JavaGradle, на основе Groovy:

plugins {
    id 'java-library'
}

repositories {
    jcenter()
}

dependencies {
    api 'org.apache.commons:commons-math3:3.6.1'

    implementation 'com.google.guava:guava:27.0.1-jre'

    testImplementation 'junit:junit:4.12'
}

Также есть CocoaPods, основанные на Ruby:

source 'http://source.git'
platform :ios, '8.0'

target 'Demo' do
    pod 'AFNetworking'
    pod 'SDWebImage'
    pod 'Masonry'
    pod "Typeset"
    pod 'BlocksKit'
    pod 'Mantle'
    pod 'IQKeyboardManager'
    pod 'IQDropDownTextField'
end

Конкретные детали реализации выходят за рамки этой статьи, поэтому вернемся к JavaScript.


Я лично требую, чтобы DSL имел эти функции:

  • Сосредоточьтесь на определенной области. Другими словами, его цель очень ясна, поэтому он намного проще, чем язык с универсальными типами, но его границы иногда нелегко понять.
  • организационный. Он должен облегчить организацию и описание проблем предметной области,Или это ограничение. Файлы конфигурации очень хорошо организованы, такие как JSON, которые могут легко описать структуру данных без какой-либо умственной нагрузки. Другим типичным примером являются фреймворки модульного тестирования (такие как jest), которые используют компоненты description, it и ожидают, чтобы сделать модульные тесты более организованными.
  • удобочитаемость. Он должен быть удобочитаемым и понятным для человека.
  • декларативный. Декларативный лучше, чем процедурный, описывающий что, а не как.
  • Расширяемость. Многие DSL поначалу не заостряют на этом внимание, потому что поначалу проблема может быть несложной.Проблемная область не статична, она может увеличиваться, и в настоящее время критична масштабируемость DSL.. Как и в случае с HTML, по мере того, как интерфейсная разработка становится все более и более сложной, исходный набор элементов и функций больше не может удовлетворить потребности, поэтому создается множество компонентов или схем пользовательских элементов. Если исходный DSL не может быть расширен, поверх него можно создать слой DSL, например, CSS против SASS, HTML против React.


2. Внутренний DSL JavaScript

В предыдущем разделе упоминалось, что Groovy и Ruby «пригодны» для использования в качестве материнских DSL. лаконичный.

«JavaScript DSL» Google соответствует очень немногим действительным источникам. Если вы чувствуете себя запутанным, вы должны вернуться к самой проблеме, Самое главное решить проблему домена, а то, как ее организовать и описать, является относительно второстепенным. Так что не беспокойтесь о том, подходит JavaScript или нет.


Затем давайте поговорим о типичной организации внутреннего DSL JavaScript для конкретного поля Mock Server:


2.1 Форма объекта

Самый простой способ сделать это — объявить его непосредственно на основе объекта или массива, что является простым и организованным. НапримерUmi Mockа такжелетающий ледMock, основан на организации объекта:

export default {
  // 支持值为 Object 和 Array
  'GET /api/users': { users: [1, 2] },

  // GET POST 可省略
  '/api/users/1': { id: 1 },

  // 支持自定义函数,API 参考 express@4
  'POST /api/users/create': (req, res) => {
    res.end('OK')
  },

  // 使用 mockjs 等三方库
  'GET /api/tags': mockjs.mock({
    'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
  }),
}

Как и в случае с конфигурационным файлом, реализация и использование очень просты. Простые сценарии API Mock доступны из коробки. Для сложного использования и протоколов API они также могут быть дополнительно инкапсулированы пользовательскими функциями.Но иногда нам хочется, чтобы библиотека брала на себя немного больше.



2.2 Форма цепного вызова

Другой типичной формой использования JavaScript в качестве внутреннего DSL является цепочка.

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

$('.awesome')
  .addClass('flash')
  .draggable()
  .css('color', 'red')

Режим API JQuery также повлиял на другие поля, такие как поле IoT.Ruff:

$.ready(function(error) {
  if (error) {
    console.log(error)
    return
  }

  // 点亮灯
  $('#led-r').turnOn()
})

jest

expect(z).not.toBeNull()
expect(z).toBeDefined()
expect(value).toBeGreaterThan(3)
expect(value).toBeGreaterThanOrEqual(3.5)

Есть также два таких примера в области сервера API Mock:

Nock:

const scope = nock('http://myapp.iriscouch.com')
  .get('/users/1')
  .reply(404)
  .post('/users', {
    username: 'pgte',
    email: 'pedro.teixeira@gmail.com',
  })
  .reply(201, {
    ok: true,
    id: '123ABC',
    rev: '946B7D1C',
  })
  .get('/users/123ABC')
  .reply(200, {
    _id: '123ABC',
    _rev: '946B7D1C',
    username: 'pgte',
    email: 'pedro.teixeira@gmail.com',
  })

И команда NetEase CloudSrvx

get('/handle(.*)').to.handle(ctx => {
  ctx.body = 'handle'
})
get('/blog(.*)').to.json({ code: 200 })
get('/code(.*)').to.send('code', 201)
get('/json(.*)').to.send({ json: true })
get('/text(.*)').to.send('haha')
get('/html(.*)').to.send('<html>haha</html>')
get('/rewrite:path(.*)').to.rewrite('/query{path}')
get('/redirect:path(.*)').to.redirect('localhost:9002/proxy{path}')
get('/api(.*)').to.proxy('http://mock.server.com/')
get('/test(.*)').to.proxy('http://mock.server.com/', {
  secure: false,
})
get('/test/:id').to.proxy('http://{id}.dynamic.server.com/')
get('/query(.*)').to.handle(ctx => {
  ctx.body = ctx.query
})
get('/header(.*)')
  .to.header({ 'X-From': 'svrx' })
  .json({ user: 'svrx' })
get('/user').to.json({ user: 'svrx' })
get('/sendFile/:path(.*)').to.sendFile('./{path}')

Шаблон цепочечного вызова в настоящее время является основной формой внутреннего DSL JavaScript. И реализация относительно проста,Что еще более важно, это близко к естественному языку.



2.3 ES2015 Template Tag

В последние годы на основеES6 Template TagБиблиотеки для функций, вводящих «новые языки» в JavaScript, бесконечны.

Однако, поскольку тег шаблона ES6 по сути представляет собой строку, ее необходимо проанализировать и преобразовать, поэтому он больше похож на внешний DSL. Не забывайте о компиляторе как о структуре Часто мы можем использовать плагины Babel для преобразования их в код JavaScript заранее во время компиляции.


Назовем несколько популярных примеров:

Zebu: это небольшой компилятор, предназначенный для разбора тегов шаблонов, посмотрите некоторые из его встроенных примеров:

// 范围
range`1,3 ... (10)` // [1, 3, 5, 7, 9]

// 状态机, 牛逼
const traffic = machine`
  initState: #green
  states: #green | #yellow | #red
  events: #timer
  onTransition: ${state => console.log(state)}

  #green  @ #timer -> #yellow
  #yellow @ #timer -> #red
  #red    @ #timer -> #green
`
traffic.start() // log { type: "green" }
traffic.send({ type: 'timer' }) // log { type: "yellow" }

Тест на шуточном столе:

describe.each`
  a    | b    | expected
  ${1} | ${1} | ${2}
  ${1} | ${2} | ${3}
  ${2} | ${1} | ${3}
`('$a + $b', ({ a, b, expected }) => {
  test(`returns ${expected}`, () => {
    expect(a + b).toBe(expected)
  })

  test(`returned value not be greater than ${expected}`, () => {
    expect(a + b).not.toBeGreaterThan(expected)
  })

  test(`returned value not be less than ${expected}`, () => {
    expect(a + b).not.toBeLessThan(expected)
  })
})

Кроме того:


Тег шаблона Эти схемы открыли нам много дыр в мозгу. Однако это также вносит некоторую сложность, как было сказано в начале, это строки, которые нужно разбирать, проверять синтаксис и преобразовывать, а языковой механизм самого JavaScript не приносит им особого удобства (например, подсветка синтаксиса), типа проверка).



2.4 Хотите попробовать JSX?

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

Давайте сначала посмотрим на дизайн прототипа нашего Mock-сервера:

import { Get, Post, mock } from 'jsxmock'

export default (
  <server port="4321">
    {/* 首页 */}
    <Get>hello world</Get>
    {/* 登录 */}
    <Post path="/login">login success</Post>
    {/* 返回 JSON */}
    <Get path="/json">{{ id: 1 }}</Get>
    {/* mockjs */}
    <Get path="/mockjs">{mock({ 'id|+1': 1, name: '@name' })}</Get>
    {/*自定义逻辑*/}
    <Get path="/user/:id">{(req, res) => res.send('hello')}</Get>
  </server>
)

Сценарии вложенных матчей

export default (
  <server>
    <Get path="/api">
      {/* 匹配 /api?method=foo */}
      <MatchBySearch key="method" value="foo">
        foo
      </MatchBySearch>
      {/* 匹配 /api?method=bar */}
      <MatchBySearch key="method" value="bar">
        bar
      </MatchBySearch>
      <BlackHole>我会吃掉任何请求</BlackHole>
    </Get>
  </server>
)

Немного подробностей? Далее инкапсулирует компонент:

const MyAwesomeAPI = props => {
  const { path = '/api', children } = props
  return (
    <Get path={path}>
      {Object.keys(children).map(name => (
        <MatchBySearch key="method" value={name}>
          {children[name]}
        </MatchBySearch>
      ))}
    </Get>
  )
}

export default (
  <server>
    <MyAwesomeAPI>{{ foo: 'foo', bar: 'bar' }}</MyAwesomeAPI>
    <MyAwesomeAPI path="/api-2">{{ hello: 'foo', world: 'bar' }}</MyAwesomeAPI>
  </server>
)

выглядит хорошо да? Мы увидели потенциал JSX как DSL и перенесли компонентное мышление React за рамки графического интерфейса.




Вы знаете мой стиль, он длиннее ☕️ Сделайте перерыв и читайте дальше.



3. Начало работы с JSX

Если вы разработчик React, JSX должен быть слишком знаком. Это не более чем синтаксический сахар, но в настоящее время он не является частью стандарта JavaScript. Babel и Typescript поддерживают транспиляцию JSX.

Например

const jsx = (
  <div foo="bar">
    <span>1</span>
    <span>2</span>
    <Custom>custom element</Custom>
  </div>
)

будет перевести на:

const jsx = React.createElement(
  'div',
  {
    foo: 'bar',
  },
  React.createElement('span', null, '1'),
  React.createElement('span', null, '2'),
  React.createElement(Custom, null, 'custom element')
)

3.1 Пользовательская фабрика

JSX требуетзаводской методдля создания «экземпляра узла». По умолчаниюReact.createElement. Мы можем запросить плагин транспиляции, аннотировав конфигурацию. По соглашению пользовательские фабрики называютсяh:

/* @jsx h */
/* @jsxFrag 'fragment' */
import { h } from 'somelib'

const jsx = (
  <div foo="bar">
    <span>1</span>
    <span>2</span>
    <>fragement</>
  </div>
)

переведет на:

import { h } from 'somelib'

const jsx = h(
  'div',
  {
    foo: 'bar',
  },
  h('span', null, '1'),
  h('span', null, '2'),
  h('fragment', null, 'fragement')
)

3.2 Host Component vs Custom Component

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

// 内置组件
;<div />
// 自定义组件
;<Custom />

3.3 Простая реализация фабричного метода createElement

export function createElement(type, props, ...children) {
  const copy = { ...(props || EMPTY_OBJECT) }
  copy.children = copy.children || (children.length > 1 ? children : children[0])

  return {
    _vnode: true,
    type,
    props: copy,
  }
}


4. Дизайн основных компонентов

4.1 Вдохновленный Коа

Вы должны быть знакомы с механизмом промежуточного программного обеспечения koa.

// logger
app.use(async (ctx, next) => {
  await next()
  const rt = ctx.response.get('X-Response-Time')
  console.log(`${ctx.method} ${ctx.url} - ${rt}`)
})

// x-response-time
app.use(async (ctx, next) => {
  const start = Date.now()
  await next()
  const ms = Date.now() - start
  ctx.set('X-Response-Time', `${ms}ms`)
})

// response
app.use(async ctx => {
  ctx.body = 'Hello World'
})

Визуально говоря, это луковая модель:


Когда промежуточное ПО вызовет следующий, оно перейдет на следующий уровень. Если граница функции нарушена. Похоже на луковицу:


Я обнаружил, что эта луковая структура более интуитивно представлена ​​с помощью JSX.




4.2 использовать основные компоненты

Итак, есть<use />этот базовый компонент. Он похож на Коаapp.use, используется для перехвата запроса, вы можете ответить или выбрать переход на следующий уровень.

① Давайте посмотрим на общий дизайн.

Использование основано на вышеизложенном, используя JSX для описания основных компонентов иерархии пакетов промежуточного программного обеспечения. Поскольку используется древовидная структура, необходимо различатьПО промежуточного слоя Brotherа такжедочернее промежуточное ПО:

<server>
  <use m={A}>
    <use m={Aa} />
    <use m={Ab} />
  </use>
  <use m={B} />
  <use m={C} />
</server>

вAa,Abто естьAиздочернее промежуточное ПО. В A вы можете назвать что-то вроде koanextдля входа в промежуточное ПО более низкого уровня.

A,B,CмеждуПО промежуточного слоя Brother. Если предыдущее промежуточное ПО-преемник не совпадает, выполняется следующее соседнее промежуточное ПО.

На первый взгляд, это сочетание коа и экспресса!


② Еще раз посмотрите на дизайн реквизита.

interface UseProps {
  m: (req, res, recurse: () => Promise<boolean>) => Promise<boolean>;
  skip?: boolean;
}
  • m

    • req,res: объект экспресс-запроса и объект ответа.

    • recurse: рекурсивно выполнять дочернее промежуточное ПО, аналогично следующему koa. вернутьPromise<boolean>, он будет разрешен после выполнения промежуточного программного обеспечения нижнего уровня, а логическое значение указывает, соответствует ли промежуточное программное обеспечение более низкого уровня и перехватывает ли запрос.

    • Возвращаемое значение: вернутьPromise<boolean>Указывает, соответствует ли текущее промежуточное ПО (перехватывает запросы). Если оно совпадает, то последующее ПО промежуточного слоя не будет выполняться.

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


③ Взгляните на рабочий пример

Предположим, что код:

const cb = name => () => {
  console.log(name)
  return false
}

export default (
  <server>
    <use
      m={async (req, res, rec) => {
        console.log('A')
        if (req.path === '/user') await rec() // 如果匹配,则放行,让其递归进入内部
        console.log('end A')
        return false
      }}
    >
      <use m={cb('A-1')}>如果父级匹配,则这里会被执行</use>
      <use m={cb('A-2')}>...</use>
    </use>
    <use m={cb('B')} />
    <use m={cb('C')} />
  </server>
)

Если запрашивается '/', то выполняется печатьA -> end A -> B -> C; Если запрос '/user', то напечатайтеA -> A-1 -> A-2 -> end A -> B -> C


Наши базовые компоненты такие же, как у Koa/Express, ядро ​​очень маленькое и лаконичное, конечно, оно также более низкого уровня, что обеспечивает гибкость.

Этот простой базовый дизайн компонентов является «краеугольным камнем» всей структуры.. Если вы знаете Koa и Express, здесь нет ничего нового. Это просто еще один способ выразить это.



4.3 Инкапсуляция высокоуровневых компонентов

Хорошо, естьuseС помощью этого базового примитива я могу делать много интересных вещей и инкапсулировать высокоуровневые API, используя компонентное мышление.


<Log>: журнал

Инкапсулируйте простейший компонент:

export const Log: Component = props => {
  return (
    <use
      m={async (req, res, rec) => {
        const start = Date.now()
        // 进入下一级
        const rtn = await rec()
        console.log(
          `${req.method} ${req.path}: ${Date.now() - start}ms`
        )
        return rtn
      }}
    >
      {props.children}
    </use>
  )
}

Применение:

<server>
  <Log>
    <Get>hello world</Get>
    <Post path="/login">login sucess</Post>
    ...
  </Log>
</server>


<NotFound>: 404

export const NotFound = props => {
  const { children } = props
  return (
    <use
      m={async (req, res, rec) => {
        const found = await rec()
        if (!found) {
          // 下级未匹配
          res.status(404)
          res.send('Not Found')
        }
        return true
      }}
    >
      {children}
    </use>
  )
}

Использование такое же, как и в журнале.recurseКогда возвращается false, это означает, что подчиненный не соответствует запросу.



<Catch>: Обработка исключений

export const Catch: Component = props => {
  return (
    <use
      m={async (req, res, rec) => {
        try {
          return await rec()
        } catch (err) {
          res.status(500)
          res.send(err.message)
          return true
        }
      }}
    >
      {props.children}
    </use>
  )
}

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



<Match>: запрос соответствия

Компонент Match также является очень простым компонентом, и на его основе реализованы другие высокоуровневые компоненты. Он используется для сопоставления запросов и ответа. Давайте сначала взглянем на дизайн Props:

export type CustomResponder =
  | MiddlewareMatcher
  | MockType
  | boolean
  | string
  | number
  | object
  | null
  | undefined

export interface MatchProps {
  match?: (req: Request, res: Response) => boolean // 请求匹配
  headers?: StringRecord // 默认响应报头
  code?: number | string // 默认响应码
  // children 类型则比较复杂, 可以是原始类型、对象、Mock对象、自定义响应函数,以及下级中间件
  children?: ComponentChildren | CustomResponder
}

Соответствие телу компонента:

export const Match = (props: MatchProps) => {
  const { match, skip, children } = props
  // 对 children 进行转换
  let response = generateCustomResponder(children, props)

  return (
    <use
      skip={skip}
      m={async (req, res, rec) => {
        // 检查是否匹配
        if (match ? match(req, res) : true) {
          if (response) {
            return response(req, res, rec)
          }
          // 如果没有响应器,则将控制权交给下级组件
          return rec()
        }

        return false
      }}
    >
      {children}
    </use>
  )
}

Из-за нехватки места конкретные детали Match можно найти вздесь

вперед, вперед.Get,Post,Delete,MatchByJSON,MatchBySearchвсе вMatchОсновываясь на пакете, он не будет здесь расширен.



<Delay>: запоздалый ответ

Я был так взволнован, что случайно написал слишком длинно, чтобы написать буклет. Ок, последний пример, будет сценарий имитации отложенного ответа в Mock API, реализация очень простая:

export const Delay = (props: DelayProps) => {
  const { timeout = 3000, ...other } = props
  return (
    <use
      m={async (req, res, rec) => {
        await new Promise(res => setTimeout(res, timeout))
        return rec()
      }}
    >
      <Match {...other} />
    </use>
  )
}

Применение:

<Get path="/delay">
  {/* 延迟 5s 返回 */}
  <Delay timeout={5000}>Delay Delay...</Delay>
</Get>

Дополнительные варианты использования см.jsxmock документация)

Его здесь придерживаться непросто, возможно, вам будет интересен его принцип, так что можете продолжать читать.



5. Говоря о принципе реализации

Просто взгляните на реализацию. Если вы знаете принцип реализации React или Virtual-DOM. Все это легко понять.


5.1 «Визуализация»

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

Наша цель — собрать все промежуточное ПО и их отношения вложенности. Для их хранения мы используем древовидную структуру данных MiddlewareNode:

export type Middleware = (
  req: Request,
  res: Response,
  // 递归
  recurse: () => Promise<boolean>,
) => Promise<boolean>

export interface MiddlewareNode {
  m: Middleware           // 中间件函数
  skip: boolean           // 是否跳过
  children: MiddlewareNode[] // 子级中间件
}

функция визуализации:

let currentMiddlewareNode
export function render(vnode) {
  // ...
  // 🔴 创建根中间件
  const middlewares = (currentMiddlewareNode = createMiddlewareNode())
  // 🔴 挂载
  const tree = mount(vnode)
  // ...
}

Монтирование — это рекурсивный процесс.自定义组件Мы будем расширяться, и мы будем собирать их, когда столкнемся с компонентами использования.currentMiddlewareNodeсередина:

function mount(vnode) {
  let prevMiddlewareNode
  if (typeof vnode.type === 'function') {
    // 🔴自定义组件展开
    const rtn = vnode.type(vnode.props)
    if (rtn != null) {
      // 递归挂载自定义组件的渲染结果
      mount(rtn, inst)
    }
  } else if (typeof vnode.type === 'string') {
    // 内置组件
    if (vnode.type === 'use') {
      // 🔴收集中间件
      const md = createMiddlewareNode(inst.props.m)
      md.skip = !!inst.props.skip
      currentMiddlewareNode.children.push(md)

      // 保存父级中间件
      prevMiddlewareNode = currentMiddlewareNode
      currentMiddlewareNode = md // ⬇️推入栈,下级的中间件将加入这个列表
    } else {
      // ... 其他内置组件
    }

    // 🔴递归挂载子级节点
    mountChilren(inst.props.children, inst)

    if (vnode.type === 'use') {
      currentMiddlewareNode = prevMiddlewareNode // ⬆️弹出栈
    }
  }
}

// 🔴 子节点列表挂载
function mountChilren(children: any, parent: Instance) {
  childrenToArray(children).forEach(mount)
}


5.2 Эксплуатация

Теперь посмотрите, как это работает. Мы реализовали простой промежуточный механизм, который легче понять, чем Koa:

export async function runMiddlewares(req, res, current): Promise<boolean> {
  const { m, skip, children } = current
  if (skip) {
    // 跳过, 直接返回 false
    return false
  }
  // 调用中间件
  return m(req, res, async () => {
    // recurse 回调
    // 🔴 如果有下级中间件,则递归调用子级中间件
    if (children && children.length) {
      for (const child of children) {
        const matched = await runMiddlewares(req, res, child)
        if (matched) {
          // 🔴 如果其中一个兄弟中间件匹配,后续的中间件都不会被执行
          return true
        }
      }
    }

    return false // 🔴 没有下级中间件,或者没有任何下级中间件匹配
  })
}

очень просто да? рекурсивная рекурсия



6. Заключение, все наконец закончилось

В этой статье рассказывается о DSL из файлов конфигурации, а также о внутренних выражениях и возможностях DSL JavaScript. Наконец, основное внимание уделяется JSX.

Я показываю компонентное мышление JSX и React на практическом примере, это не только подходит для описания пользовательского интерфейса, мы также видим потенциал и гибкость JSX как DSL.

Наконец, суммируйте преимущества и недостатки.


✅ Преимущества

  • Улучшенный вывод типов и ограничения. Подходит для машинописного текста
  • комбинируемый. Благодаря возможностям инкапсуляции и композиции компонентов можно легко инкапсулировать высокоуровневые и простые в использовании интерфейсы.
  • Just Javascript. Это сам код JavaScript, очень гибкий
  • Лучшая организация, сравнимая с файлами конфигурации. Синтаксис JSX похож на XML и хорошо организован.
  • Привычка. Если вы привыкли к интерфейсным средам, таким как React и Vue, метод настройки JSX легко принять и начать работу с ним.
  • Простота реализации.
  • Более интуитивное представление иерархической структуры. Например, луковичная структура, представляющая промежуточное ПО
  • модульный. По своей природе интерфейсы могут быть распределены по разным файлам, которые потом можно легко комбинировать.

⚠️ Недостатки

  • Код нужно перевести. Требуется транспиляция Babel и Typescript.
  • Типа многословный.

гибкий, но организованный. Гибкость часто приводит к беспорядку, а организация может означать отказ от гибкости, что в некотором смысле противоречиво. Редко удается сбалансировать их, и JSX может быть одним из них. (Кажется, я дую 🐂)


🎉🎉Код уже есть на Github, в данный момент находится на стадии прототипа:ivan-94/jsxmock⭐️ и комментарии приветствуются.



7. Расширение