В этой статье мы кратко представим нашу текущую архитектуру интерфейса беспроводной сети и путь ее эволюции.
Основное содержание разделено на несколько частей:
1) Текущие интерфейсные решения и проблемы, которые они решают
2) Новые вызовы, с которыми сейчас сталкиваются
3) Дизайн и выбор нашего внешнего решения.
Надеюсь, наш опыт вдохновит вас.
1. Текущие интерфейсные решения и проблемы, которые они решают
1.1. Техническая основа действующей схемы
Перемотать время в 2016 год. Мы перенесли несколько основных интерфейсных приложений с C#.ASP.NETПерешел на Node.js. А во фронтенд-фреймворк на основе Backbone.js добавлен React для управления уровнем представления, заменяя механизм шаблонов Underscore.js, реализуя полное разделение фронтенда и бэкэнда.
Внедрить React в старый фреймворк не так просто, как описано выше. Нам нужно решить 2 задачи.
1) Реакция слишком велика
2) Для разработки React требуется поддержка инструментов компиляции ES2015 и JSX.
В то время существующий фреймворк уже был огромным, и введение React увеличило бы размер JS еще на 140+ Кбайт, что еще больше замедлило бы время первого рендеринга нашего SPA. Это было неприемлемо и являлось важным фактором, который в то время мешал большинству компаний использовать React в своих первоначальных фронтенд-проектах.
React слишком велик, если это не новый проект или рефакторинг, есть шанс перераспределить бюджет размера JS. В противном случае, если вы хотите использовать новую технологию для решения проблем существующих проектов, вы должны сначала решить вопрос о стоимости внедрения новой технологии.
Чтобы использовать технологию компонентизации React для решения проблемы сложного обслуживания больших блоков шаблонов рендеринга. Мы разработали облегченную версию react-lite, совместимую с React API. Уменьшен размер 140+Кб до приемлемого уровня 20+Кб.
Инструментом управления модулями для нашего проекта в то время был require.js. Мы пишем код в синтаксисе ES5, и он запускается прямо в браузере. В настоящее время нет компиляции и упаковки Webpack/Babel.
Хотя react-lite уменьшает размер введения React, наша цель — использовать компонентный метод для разложения огромного кода шаблона рендеринга на несколько небольших компонентов, что удобно для обслуживания и повышает возможность повторного использования. Вы не можете использовать синтаксис JSX, вам нужно вручную писать вызовы функций React.createElement, а компоненты React могут быть более сложными в обслуживании, чем шаблоны Underscore.js.
Мы попытались использовать Webpack для замены require.js и запустить весь проект, потому что Webpack поддерживает компиляцию модулей AMD для require.js. Но вскоре мы обнаружили огромную проблему: существующий фреймворк сильно зависит от динамических модулей и удаленных модулей require.js.
Динамический модуль означает, что он будет оценивать разные среды и объединять разные URL-адреса, например:
require('/path/to/' + isInApp ? 'hybrid' : 'h5')
Удаленный модуль означает, что существует множество модулей, представляющих собой js-скрипты, выдаваемые через http-запросы, и они не находятся в локальной директории проекта.
Это делает трудно использовать WebPack на основе анализа зависимостей локального модуля. Существуют различные другие тривиальные проблемы, не так смертные, как и выше, но и мешают нам мигрировать нашу интерфейсную инфраструктуру из потребностей в WebPack + Babel.
Наконец, мы разработали схему деэскалации. Он не только сохраняет механизм работы require.js, но также использует новый синтаксис JSX/ES2015 для разработки компонентов React.
Мы настроили каталоги ES6 и ES5 и на основе Gulp + Babel создали задачу сценария в реальном времени, которая компилирует модули ES6 в модули ES5 в соответствии с изменениями файлов. При разработке просто запустите команду gulp.
С помощью вышеперечисленных приемов мы успешно продвигали шаблоны разработки ES6 и React в нашей команде. Это заложило для нас хорошую основу для создания нового метода разработки интерфейса на основе React + Node.js + Webpack + Babel.
1.2 Текущее решение: рождение изоморфного фреймворка React-IMVC
Внедрение метода разработки Node.js + React + ES2015 в существующие проекты действительно помогло нашей фронтенд-разработке. Мы можем писать более лаконичный и элегантный код ES2015, и нам больше не нужно поддерживать шаблоны .cshtml и настраивать серверы IIS для запуска наших приложений SPA.
В фронтенд-проекте нет кода и конфигурации на других языках, а для достижения самосогласованности и самообслуживания используется только JavaScript.
Тем не менее, мы все еще работаем над нашим интерфейсным приложением под тяжелым историческим техническим бременем. Это не долгосрочное решение.
Нам нужна новая архитектура интерфейса, которая в большей степени использует модель Node.js + React с точки зрения 2016, а не 2012 года.
Это необходимо для достижения следующих целей:
1) Одна команда для запуска полной среды разработки
2) Одна команда для компиляции и сборки исходного кода
3) Код, который можно использовать для рендеринга на стороне сервера (SSR) в node.js или продолжить рендеринг после мультиплексирования на стороне браузера (CSR и SPA).
4) Это как многостраничное приложение, так и одностраничное. Вы также можете свободно переключаться между двумя режимами через конфигурацию и использовать «изоморфное приложение», чтобы преодолеть дилемму «одностраничное VS многостраничное».
5) Статический файл в режиме истории хэшей может быть сгенерирован при построении как входной файл (SPA) обычного одностраничного приложения.
6) При сборке можно резать код по маршруту и подгружать js файл по требованию
7) Поддержка использования новых языковых функций ES2015+, включая async/await в IE9 и более поздних версиях браузеров.
8) Богатый жизненный цикл, так что бизнес-код имеет более четкое разделение функций
9) Автоматически разрешать html и данные, отображаемые сервером на стороне браузера, и плавно переходить
10) Простые в использовании изоморфные методы выборки, перенаправления и файлов cookie и т. д. посредством внешних и внутренних запросов, перенаправления и операций с файлами cookie.
Внимательные студенты могут обнаружить, что использование Next.js напрямую не может достичь вышеуказанных целей?
Оно делает.
Тем не менее, Next.js не появится до октября 2016 года и постепенно станет широко известен ближе к 2018 году. У нас нет времени ждать, пока будущие фреймворки решат сегодняшние проблемы.
Итак, в июле 2016 года я разработал библиотеку create-app, которая реализовала минимальные изоморфные основные функции и добавила store, fetch, cookie, redirect, webpack, babel, ssr/csr на основе create-app, config и других. функции образуют нашу собственную изоморфную структуру React-IMVC, которая достигает 10 основных целей.
1.3 Идеи дизайна React-IMVC
Разобьем каждую страницу на 3 части: Model, View и Controller. Вернемся к самой наивной ментальной модели MVC для разработки GUI. Это видно из названия фреймворка React-IMVC.
I в IMVC — это аббревиатура от Isomorphic, что означает изоморфизм, Здесь он относится к фрагменту кода JavaScript, который может выполняться в Node.js или в браузере.
M IMVC M - это аббревиатура модели, означающая модель, вот коллекция состояния и его функция изменения статуса, состоящая из функциональной функции и действий и действий.
V в IMVC — это аббревиатура от View, что означает представление, и здесь это относится к компоненту React.
C в IMVC относится к аббревиатуре контроллера, что означает контроллер, который здесь относится к методам жизненного цикла, обработчикам событий, методам изоморфных инструментов и промежуточным средам, отвечающим за синхронизацию представления и модели.
Все три части MVC в React-IMVC изоморфны, поэтому он может: писать только один код, выполнять рендеринг на стороне сервера на стороне сервера в Node.js и выполнять рендеринг на стороне клиента в браузере. рендеринг.
В модели React-IMVC используется режим Redux, но сделаны некоторые упрощения, чтобы сократить написание шаблонного кода. Среди них состояние — неизменяемые данные, а действие — чистая функция, не содержащая побочных эффектов.
Вид React-IMVC — это React, рекомендуется максимально использовать написание функциональных компонентов и не рекомендуется включать побочные эффекты.
Однако побочные эффекты являются неизбежным продуктом взаимодействия с внешним миром и могут быть только изолированы, но не устранены. Поэтому нам нужен объект, который берет на себя побочные эффекты, то есть контроллер.
Методы жизненного цикла являются источником побочных эффектов, Ajax/Fetch также является источником побочных эффектов, обработчик событий также является источником побочных эффектов, а localStorage также является источником побочных эффектов, и все они должны обрабатываться в объектно-ориентированный способ в классах ES2015, контроллер.
Веб-приложение содержит несколько страниц Page, и каждая страница состоит из трех частей MVC.
Приведенный выше код реализует страницу счетчика, которая поддерживает SSR/CSR. Мы можем ясно видеть философию дизайна React-IMVC.
Свойство Model класса Controller описывает начальное состояние модели, initialState, и определяет действия по изменению состояния.
Свойство View класса Controller описывает представление представления через компонент React, который выполняет привязку данных и привязку событий на основе состояния/действий, предоставляемых Моделью.
Когда событие щелчка слоя представления инициирует действия, это приведет к изменению внутреннего состояния модели, а изменение модели уведомит контроллер о необходимости инициировать обновление слоя представления. Это представляет собой классическую модель цикла рендеринга модели, представления и контроллера.
Итак, как мы поддерживаем SSR?
Как показано на рисунке выше, это очень просто, контроллер содержит множество жизненных циклов, в которых getInitialState будет вызываться перед созданием экземпляра модели/хранилища, поддерживает асинхронность и может использовать API-интерфейс выборки, предоставленный контроллером, для создания http-интерфейса. Запросы.
React-IMVC будет удерживать асинхронный сбор данных внутри, и последующий процесс рендеринга не будет выполняться, пока данные SSR не будут готовы. Эти сложные операции скрыты внутри фреймворка. Для разработчиков страниц это просто жизненный цикл, асинхронные вызовы интерфейса.
Помимо getInitialState, React-IMVC также предоставляет другие полезные жизненные циклы, такие как:
1) shouldComponentCreate: Должна ли страница отображаться? Здесь можно перенаправить аутентификацию и this.redirect.
2) pageWillLeave: страница собирается перейти на другие страницы
3) pageDidBack: страница возвращается с других страниц
4) windowWillUnload: окно вот-вот закроется
5) …
Настроив богатый жизненный цикл, мы можем более четко сегментировать бизнес-код.
При использовании index.js в качестве модуля маршрутизации Controller.js нескольких страниц устанавливаются в соответствии с теми же правилами конфигурации пути/маршрутизатора, что и Express.js, поэтому различные запросы страниц могут загружаться и отвечать на них по запросу.
Платформа React-IMVC возьмет на себя запрос в Node.js, сопоставит соответствующий модуль контроллера в соответствии с путем запроса Request.pathname и выполнит создание экземпляра и работу SSR. На стороне браузера фреймворк автоматически повторно использует структуру html и данные initialState в соответствии с содержимым SSR. React называет этот процесс Hydration.
Разработчикам страниц не нужно учитывать адаптацию SSR в большинстве сценариев. Внутри методов {fetch, get, post, cookie, redirect} и других в контроллере соответствующая реализация кода будет автоматически переключаться в соответствии с исполняемой средой, которая прозрачна для пользователя.
Благодаря изоморфному фреймворку React-IMVC мы усовершенствовали и стандартизировали метод разработки интерфейсных проектов. В течение нескольких лет большое количество старых проектов было перенесено на новый фреймворк, и почти все новые проекты разрабатывались на основе нового фреймворка, что привело нашу команду в эпоху современной веб-разработки, современного стека технологий фронтенд-разработки. .
2. Текущие новые вызовы и проблемы
При разработке фреймворка React-IMVC мы рассчитывали, что решение будет применимо в течение 5 лет и не устареет. Теперь, спустя более 3 лет, в передней части произошли некоторые интересные изменения. Например, появление React-Hooks в октябре 2018 года, как и популярность TypeScript.
Эти прогрессивные усовершенствования не делают структуру SSR устаревшей. React-IMVC также своевременно доработал поддержку React-Hooks и TypeScript.
Давайте еще раз остановимся и пересмотрим дизайн новой архитектуры интерфейса, а не то, что существующие решения снова устарели. Скорее, мы сталкиваемся с новыми проблемами, для которых существующие решения недостаточны.
В начале разработки фреймворка React-IMVC основное внимание уделялось объединению двух платформ Node.js + Browser. Пусть часть кода запускается в Node.js и браузере одновременно и автоматически координирует процесс гидратации между сервером и браузером. React-IMVC по-прежнему является разумным выбором для приложений, которые предполагают разделение веб-разработки только на интерфейсную и серверную часть.
При столкновении со сценарием с несколькими терминалами + интернационализацией ситуация выходит за рамки первоначального рассмотрения. Линейка продуктов может иметь несколько приложений:
1) Отечественный сайт ПК;
2) Международный компьютерный сайт
3) Внутренний сайт H5
4) Международный сайт H5
5) React-Native приложения в домашних приложениях
6) Приложение React-Native в международном приложении
7) Внутреннее приложение мини-программы
8) Приложения в других дистрибутивах или каналах и т. д.
При таком большом количестве форм приложений у каждого есть штатная команда разработчиков интерфейса, а его стоимость и эффективность неудовлетворительны. React-IMVC подходит для однородных интерфейсных приложений для ПК/H5, но не поддерживает App/React-Native и небольшие программы. Вопрос о том, как сэкономить на многоконечной разработке, требует серьезного рассмотрения.
Увидев это, студенты, более чувствительные к новым технологиям, могут подумать, что Flutter может решать проблемы. Flutter — это вариант, но он может не подходить для всех сценариев и команд.
2.1 Исследование сквозных решений
В какой-то степени кросс-энд — это решаемая проблема фронтенд-разработки. JavaScript может работать на ПК/мобильных устройствах, IOS/Android, в приложениях/браузерах, а веб-страницы есть везде.
Когда мы обсуждаем сквозные решения, это не вопрос способностей, а вопрос зрелости/удовлетворенности.
Через WebView/Browser интерфейс везде разрабатывается с использованием HTML/CSS/JavaScript, конечно же, это кросс-энд. Однако основные показатели, такие как скорость загрузки и беглость в приложении, не могут соответствовать требованиям. Поэтому существуют решения для улучшения, такие как React-Native: используйте JavaScript для написания бизнес-логики, используйте компоненты React для выражения абстрактных интерфейсов, но используйте собственный пользовательский интерфейс для ускорения рендеринга: Написано на JavaScript — визуализируется с помощью собственного кода.
React-Native предоставляет хорошие возможности кросс-энда IOS/Android, но у него есть две проблемы:
1) Чиновник даже не обещал кросс-энд IOS/Android, просто сказал "Учись один раз, пиши везде". Официально не поддерживаются проблемы кросс-конечной совместимости, которые необходимо упаковывать и обрабатывать самостоятельно.
2) React-Native для Интернета — это решение сообщества (react-native-web), а не официальный итеративный проект. Производительность и опыт работы на веб-стороне не могут быть полностью гарантированы. При возникновении проблемы код трудно отлаживать и изменить. Недостаток контроля.
В нашем реальном использовании React-Native — хороший выбор для приложений IOS/Android, но компиляция для веб-платформы для запуска сопряжена с определенными рисками.
Flutter утверждает, что он может использовать набор кода для работы на таких платформах, как мобильные, веб- и настольные, и разработан командой Google, стоящей за ним. Действительно очень привлекательный. В настоящее время он может не подходить для нашего сценария из-за следующих соображений:
1) Flutter использует собственный язык Google Dart, а не JavaScript. Весь бизнес-код должен быть переписан, а стоимость обучения и рефакторинга высока.
2) Поддержка Flutter для Интернета все еще находится в бета-канале, на стадии предварительного выпуска, и все еще существует определенный риск использования в производстве.
3)Функции Flutter в основном охватывают движок рендеринга.При реальном развитии бизнеса необходимо дополнительно адаптировать специфические API платформ IOS/Android/Web.Не 100% собственные функции Flutter могут решить все проблемы, и за это нужно платить Много времени и затрат на базовую конструкцию вокруг Flutter.
Поэтому на этом этапе прозвучал флаттер может быть более подходит для стартовых компаний, компании малой и средней компании или большой компании непрофильных проектов с нуля.
Краткое описание нескольких основных кросс-эндовых решений выглядит следующим образом:
1) Интернет/страница: в браузере все в порядке, но в приложении нет ничего хорошего;
2) React-Native: опыт в приложении хороший, но опыт в Broser не гарантируется;
3) Flutter: опыт работы в приложении/браузере в определенной степени гарантирован, но стоимость обучения, рефакторинга и инфраструктуры высока;
Flutter — это радикально новое решение, использующее язык и инфраструктуру, новые для разработчиков в компании. Чего мы больше хотим, так это не опрокидывать существующее накопление, а постепенно улучшать текущий план.
Не исключено, что в будущем Flutter может стать лучшим решением для единого большого фронтенда, но прежде чем это станет фактом, нам предстоит столкнуться и решить текущие проблемы, а не просто ждать идеального решения в будущем. . И мультитерминал — одна из проблем, с которыми мы сталкиваемся, интернационализация — другая.
Из-за таких факторов, как огромные культурные различия между местными пользователями и международными пользователями, мы должны подготовить как минимум два набора продуктов со значительно отличающимися стилями интерфейса и интерактивными формами. Один для внутренних пользователей, а другой для иностранных пользователей (многоязычная поддержка через I18N).
Даже если проблема с несколькими терминалами будет решена с помощью таких технологий, как Flutter, нам все равно нужно подумать о двух внутренних/международных наборах мультитерминальных приложений.Есть ли место для унификации/слияния?
3. Переход с ВОП на СС
Обратим внимание на уровень модели, который берет на себя функции управления состоянием приложения и бизнес-логики и является более общей и чистой частью.
Мы можем унифицировать слои модели многотерминальных проектов, но сохранить независимые слои представления и подключить разные слои представления к соответствующей платформе/рендереру.
Возникает вопрос, как максимизировать уровень модели, позволить слою модели взять на себя как можно больше функций и написать как можно больше кода на уровне модели?
Через эту новую призму мы рассматриваем бум фронтенд-разработки за последние 5 лет и обнаруживаем интересное явление.
Развитие последних 5 лет может быть классифицировано как маршрут View-Oriented Programming, называемый VOP (это наша собственная риторика, здесь просто для того, чтобы поделиться идеями, а не как авторитетное определение, оно должно быть ссылкой).
Будь то React/React-Native, Vue/Weex, Angular, Flutter или SwiftUI, все они представляют собой шаблоны улучшения представления на основе компонентов. Они сосредоточены на компоненте представления и постоянно улучшают выразительные возможности компонента представления, от самой базовой возможности комбинации вложения родитель-потомок до возможности управления состоянием, возможности управления побочными эффектами и взаимодействием.
Давайте посмотрим на их компонентное написание.
На картинке выше показан код компонента React.В функциональном компоненте он содержит части State и View, и они неразделимы, State — это локальная переменная, а View — отношение привязки. Хотя мы можем извлекать пользовательские хуки, чтобы их можно было повторно использовать в React-Native, когда мы используем DOM/BOM или API-интерфейсы, специфичные для RN, в useEffect для запуска setState, они связаны с конкретными платформами.
Выше приведен код Vue SFC, шаблон — это часть View, data/compted — это часть State, и они находятся во взаимном соответствии.
Выше приведен код компонента Angular, и части View и State management также находятся во взаимно однозначном соответствии.
На картинке выше показан код Stateful Widget Flutter, представление находится в методе сборки, а управление состоянием реализовано через члены и методы класса. члены и методы неразделимы внутри класса.
На картинке выше показан код SwiftUI, и компоненты также выражаются через классы.По сравнению с Flutter, представление компонентов SwiftUI находится в методе body.
Независимо от того, помещают ли они состояние/представление в функцию или в класс, между состоянием и представлением существует взаимосвязь один к одному. Состояние генерируется на основе требований к потреблению и взаимодействию с представлением, которое является реальной основной частью компонента.
Это не значит, что React, Vue и Flutter/SwiftUI делают что-то неправильно, повышение выразительности компонентов — это правильно. Это просто означает, что когда состояние и представление связаны, трудно достичь цели максимального повторного использования кода на уровне модели.
Нам нужно сделать управление состоянием независимым от представлений, чтобы управлять состоянием и его изменениями на отдельном уровне модели, независимо от того, какая View Framework находится ниже по течению.
Другими словами, мы переходим от программирования, ориентированного на представление, к программированию, ориентированному на модель, или сокращенно MOP.
От просмотра, ориентированного на программирование к ориентированному на модели программирования.
4. Выбор МОП
В текущей экосистеме JavaScript популярные решения, которые можно использовать независимо от конкретных фреймворков View, включают:
1) Редукс
2) Мобкс
3) API реактивности Vue 3.0
4) Rxjs
5) …
Раньше Redux был популярным решением для управления состоянием в React, и у него есть собственная поддержка инструментов разработки, позволяющая легко отслеживать историю изменений состояния с помощью действий. Но, учитывая, что используется слишком много шаблонного кода, реализовать функцию, которая должна охватывать несколько папок, не очень удобно. В сообществе нет недостатка в жалобах на Redux, и всякий раз, когда React добавляет новую функцию, сообщество хочет заменить Redux этой новой функцией. Есть бесконечные попытки инкапсулировать Redux в более простую форму использования, даже Redux официально предоставляет пакетное решение под названием redux/toolkit.
Mobx, возможно, является еще одним популярным решением в сообществе React после Redux, ссылаясь на стиль управления состоянием Reactive Vue. Его также можно использовать независимо от React или в сочетании с другими фреймворками представлений.
Vue 3.0 извлекает API-интерфейс внутренней реактивности в автономную библиотеку, которую также можно использовать независимо или с другими платформами представлений.
Rxjs — это режим потока данных, основанный на Rxjs, который позволяет внедрить и использовать решение State-Management в любом месте.
В общем, вы можете выбрать любую из этих 4-х библиотек, в зависимости от стиля и предпочтений вашей команды. В то же время сложно максимизировать слой Model без каких-либо улучшений и только с использованием имеющихся у них функций.
Наш выбор — Redux.
Причина относительно проста: слой модели фреймворка React-IMVC, используемый нашей командой, основан на нашей собственной реализации библиотеки Relite, которая сама по себе является упрощенной версией модели Redux, похожей на официальный стиль написания Redux/toolkit. из Редукса. Выбор Redux может продолжить наш существующий опыт и часть кода.
Кроме того, мы считаем, что действия/редукторы Redux содержат необходимые основные части предсказуемого управления состоянием, что в конечном итоге предоставляет набор действий функции обновления с Redux или без него.
Например, независимо от того, используете ли вы Mobx, Vue-Reactivity-API или Rxjs, для написания кода управления состоянием приложения Todo вы все равно получите функции обновления, такие как addTodo/removeTodo/updateTodo. Redux Devtools — это готовый зрелый инструмент для отслеживания этих действий, а другие варианты требуют дополнительных затрат на адаптацию.
5. Наш фреймворк MOP: Pure-Model
Мы внедрили фреймворк MOP на основе Redux, который поддерживает максимизацию слоя модели, называемого Pure-Model.
По сравнению со стадией VOP Redux упрощен, что позволяет слою Model выполнять меньше функций, а View — больше функций. Чистая модель на этапе MOP предназначена для усиления Redux, чтобы уровень модели брал на себя больше функций, а вид — меньше функций.
Сам Redux требует, чтобы состояние было неизменным, редуктор был чистой функцией, а IO/Side-Effects должны быть реализованы через redux-middleware. Однако redux-middleware чрезвычайно сложно использовать и понимать, оно разделяет распределение кода функции и вынуждает ее обрабатывать в двух местах, что нелегко читать и поддерживать.
Это было ограничением дизайна в 2015 году. В то время все фронтенд-сообщество не знало, как управлять побочными эффектами в чистых функциях. До выпуска React-Hooks в октябре 2018 года мы видели эффективный способ добавления взаимодействия состояний и эффектов в компоненты-функции.
React-Hooks — это усовершенствование уровня представления, которое позволяет компонентам представления выражать состояние и эффекты и может использоваться для повторного использования логики в режиме настраиваемых перехватчиков. Но идея, лежащая в основе этого, является общей, не ограничиваясь слоем View, мы можем повторно реализовать хуки на уровне модели, чтобы получить такое же улучшение возможностей.
На приведенном выше рисунке показан код Pure-Model, эквивалентный показанной выше функции счетчика React-IMVC Модель больше не привязана к свойствам контроллера вместе с представлением. Модели определяются отдельно и используются в компонентах React-DOM через открытый API React-Hooks, который также можно использовать в компонентах React-Native.
Наш демонстрационный код записывает модель и представление в один и тот же модуль JS, чтобы код можно было отобразить на одном графике. В фактической разработке уровень модели представляет собой независимый модуль, который затем используется в модулях компонентов, таких как View.H5.tsx и View.RN.tsx.
Следует отметить, что есть два хука, один — View Hooks, а другой — Model Hooks.
Pure Model setupStore — это хуки модели, которые определяют хранилище. createReactModel преобразует его в Model.useState для React-Hooks.
Итак, как Pure-Model поддерживает SSR? Без метода getInitialState, предоставляемого контроллером, и без таких интерфейсов, как выборка/публикация, как запросить данные и обновить их в хранилище?
Как показано выше, мы предоставляем встроенный API-интерфейс Model-Hooks и функции жизненного цикла, такие как setupPreloadCallback, охватывающие HTTP-запросы и события, такие как предварительная загрузка, запуск, завершение.
Зарегистрируйте функцию предварительной загрузки в setupPreloadCallback, поддержите асинхронность, вы можете получить данные через интерфейс Http и вызвать действие для обновления состояния. Этот жизненный цикл обеспечивает возможность предварительной загрузки и обновления данных до того, как внешние подписчики будут использовать состояние. Таким образом, когда внешние данные используются в первый раз, они получают полную структуру.
И setupStartCallback/setupFinishCallback — это два обратных вызова, когда Модель подписана и отписана. Когда Pure-Models используются в компонентах React, они соответствуют жизненным циклам componentDidMount и componentWillUnmount.
Model-Hooks, такие как React-Hooks или Vue-Composition-API, поддерживают написание Custom Hooks для реализации повторно используемой логики, такой как setupInitialCount выше, которую можно вызывать/повторно использовать в любом месте, поддерживающем Model-Hooks.
У нас также есть встроенные Model-Hooks, такие как setupCancel, которые могут легко создавать отменяемые асинхронные задачи и не ограничиваются HTTP-запросами. Благодаря инкапсуляции этих Model Hooks API код уровня Model станет очень ясным и элегантным, и разработчики смогут использовать разные Model-Hooks для регистрации различных жизненных циклов onXXX и инициирования различных действий в соответствии с разными сценариями.
И эти жизненные циклы не в виде плоских методов в классах, их можно группировать, нарезать, инкапсулировать и вкладывать в деревья, что является более гибким и свободным паттерном.
В Pure-Model редуктор — это чистая функция, но другие дополнительные части, такие как setupXXX, поддерживают IO/Side-Effects. Это эквивалентно размещению кода redux-middleware, который должен быть написан извне, в createReactModel.Выше приведено хранилище/действия setupStore для создания неизменного/чистого, а следующее основано на хранении/действиях для создания действий, которые поддерживают асинхронный .
Все реализации функций на самом деле обернуты в такие функции, как setupStore/setupXXX.Они только определены и не выполняются, поэтому createReactModel является чистым, он просто возвращает набор функций.
На разных платформах мы можем внедрить разные реализации, такие как setupFetch, например, в браузере мы внедряем инкапсуляцию window.fetch, в Node.js мы внедряем инкапсуляцию node-fetch, а в React-Native мы внедряем глобальные пакет .fetch.
Pure-Model использует способ построения абстракции верхнего уровня: все хуки описывают, что делать, но не ограничивают, как это должна делать базовая реализация. Когда Pure-Model работает на определенной платформе, эта часть реализации кода задается уровнем адаптации и соединения.
С абстракцией Pure-Model Redux + Model-Hooks мы можем не только поместить код управления состоянием на уровень модели, но также поместить код управления побочными эффектами управления эффектами на уровень модели. На уровне представления вам нужно только Model.useState для получения текущего состояния, Model.useActions для получения функции обновления состояния и привязки их к представлению и подписке на события.
Другими словами, уровень Model содержит реализации функций, а уровень View содержит только необходимые вызовы функций. Код реализации функции длиннее, а код вызова функции короче. Мы продолжаем извлекать реализацию функции на слой модели, тогда код слоя представления и слоя контроллера будет становиться все тоньше и тоньше.
На практике мы обнаружили, что слой Model, который мы наконец получили, содержит основной код бизнес-логики приложения, который можно запускать и тестировать независимо, а также использовать в любой среде представления. Он не только кроссплатформенный, но и обладает жизненной силой через эпоху. Когда React будет постепенно вытесняться фреймворками следующего поколения, нам не нужно выбрасывать весь код; просто реализуйте адаптацию уровня модели к новой инфраструктуре представлений.
Таким образом, код, написанный на основе чистой модели MOP Framework, становится основным активом приложения.
Когда мы оглядываемся назад, на самом деле, до того, как React/Vue и другие фреймворки представлений стали мощными, связь между слоями модели и представления изначально была отрицательной. Представление - это тонкий слой, даже просто однострочный рендеринг (шаблон, данные) шаблонного рендеринга. Основной код находится на уровне модели и уровне контроллера для управления данными и событиями.
Когда React/Vue становится основной темой фронтенд-разработки, поскольку выразительная сила компонентов представления сильнее, написание всего кода в компонентах представления стало популярной тенденцией.
Однако функции слоя модели и слоя представления до некоторой степени исключают друг друга. Нам нужно, чтобы модель была независимой, стабильной и имела долгосрочную итеративную жизнеспособность, в то время как уровень представления был изменчивым, зависимым от данных, а его жизненный цикл менялся в соответствии с тенденциями стилей пользовательского интерфейса.
Когда мы реализуем код уровня Model в слое View, мы в некотором смысле отказываемся от основной ценности уровня Model.
Так почему же все использовали модель VOP в течение 5 лет, не сталкиваясь с реальными проблемами?
Это связано с тем, что сам слой модели также разделен на несколько слоев: внешний слой модели и внутренний слой модели.Внешний слой модели является соединением с внутренним слоем модели. Слой модели к слою представления влияет только на интерфейсный уровень модели.Стабильность приложения и внутренний уровень модели, от которого зависит приложение, по-прежнему поддерживает жизнеспособность независимости, стабильности и долгосрочной итерации.
На этапе быстрой разработки фронтенд-фреймворка реконструкция всего фронтенд-проекта и обновление фреймворка также являются нормальными. Таким образом, связь между слоем модели и слоем представления не имеет большого реального значения. Это похоже на утечку памяти веб-страницы, которая не является фатальной проблемой, просто обновите ее.
Когда конкуренция интерфейсных фреймворков имеет тенденцию к стабилизации, частота рефакторинга интерфейсных проектов становится менее частой, в сочетании с многотерминальными и международными требованиями уровень модели внешнего интерфейса в сочетании со слоем просмотра начинает становиться неудобным.
Один и тот же внутренний уровень модели может подключаться к нескольким приложениям с разными стилями пользовательского интерфейса, и это конвергентная модель. Уровень модели внешнего интерфейса фактически увеличивается с увеличением интерфейса пользовательского интерфейса, который является неконвергентной моделью.
Платформа MOP Pure-Model — это попытка объединить интерфейсный уровень модели. На самом деле, он не полностью отвергает фреймворки SSR, такие как React-IMVC, он по-прежнему управляется React-IMVC в браузере/Node.js и по-прежнему управляется React-Native в приложении. По сути, он просто меняет модульность кода, реализует часть кода, накопленного в слое View и уровне Controller, и выносит его на уровень Model для обслуживания, оставляя лишь небольшое количество кода для вызовов функций в View. слой и слой контроллера.
В сочетании с нашей возможностью интеграции внутренней модели, созданной с использованием схемы GraphQL-BFF, Pure-Model, обслуживающая многосторонние службы, может запрашивать GraphQL-BFF по запросу, чтобы адаптироваться к внешнему и внутреннему взаимодействию данных на разных концах. Подробнее см. "GraphQL-BFF: интерфейсные и серверные решения для взаимодействия с данными в контексте микросервисов.》
6. Монорепо
Только Pure-Mode недостаточно, это всего лишь уровень абстракции, и что действительно управляет кодом, так это структура представления, такая как React-Native/React-DOM.
Другими словами, у нас будет несколько проектов, построенных с помощью разных каркасов, но совместно использующих только тот код, который проходит через слой модели. Затем то, как совместно использовать код в нескольких проектах, стало инженерной проблемой, которую необходимо решить.
Распространение кода уровня модели через службы управления пакетами, такие как npm, является неэффективным решением. Любые изменения требуют выпуска версии и повторной установки npm или обновления npm в каждом проекте. Трудно использовать требования эффективности быстрой разработки.
Помещение нескольких проектов в несколько репозиториев git также вызовет аналогичные проблемы.В какой репозиторий git проекта помещен код уровня модели? Или добавьте отдельный репозиторий git для уровня модели. Синхронизация кода и управление версиями для репозиториев N+1 будут в хаосе.
Более эффективное и последовательное совместное использование кода может быть достигнуто с помощью многопроектной модели с одним репозиторием Monorepo.
Например, мы размещаем наши проекты в следующей структуре каталогов:
projects/isomorphic
projects/graphql-bff
projects/react-native-01
projects/react-native-02
projects/react-dom-01
project/react-dom-02
Проект isomorphics — это проект, в котором находится уровень модели, который имеет собственный независимый package.json для управления такими задачами, как разработка и тестирование. Другие проекты в каталоге проектов могут быть созданы с использованием любых шаблонов, и несколько проектов, созданных с помощью одного шаблона, могут сосуществовать. У них также есть свои отдельные наборы для разработки, сборки и тестирования.
Сопоставьте изоморфный каталог src с каталогом src/isomorphic других проектов с помощью программных ссылок. Таким образом, исходный код уникален, но появляется в нескольких проектах, каждый из которых может импортировать общий код. Когда проекту больше не нужно делиться кодом с другими проектами, он может перенести всю папку в другой независимый репозиторий git, чтобы выполнить свою собственную независимую итерацию.
Затем также вводятся внутренние проекты модели GraphQL-BFF, такие как проекты/graphql-bff, а файлы TypeScript типов данных интерфейса генерируются с помощью схемы GraphQL и совместно используются всеми внешними проектами. Мы можем получить более авторитетные подсказки по типам данных интерфейса, уменьшая большинство проблем, вызванных несоответствием между внешними и внутренними структурами и типами данных, такими как null/non-null, несогласованные типы и неправильное написание имен полей.
С помощью Monorepo мы получаем удобный способ совместного использования кода между несколькими проектами; с помощью Pure-Model мы максимизируем возможность повторного использования кода на внешнем уровне модели; с помощью GraphQL-BFF мы организуем внутреннюю модель и предоставляем авторитетный источник типов данных интерфейса; с помощью React-IMVC мы получаем возможность рендеринга SSR и CSR в Node.js и браузере; с помощью React-Native мы получаем опыт создания приложения, близкого к Native, на платформах IOS и Android. Вместе они составляют наше кросс-энд решение для повторного использования кода,
Первоначально мы думали, что для решения проблемы избыточной разработки нескольких приложений, вызванной несколькими терминалами и интернационализацией, нам нужно использовать такие технологии, как Flutter, для внесения потрясающих изменений. Однако после изучения и размышлений было обнаружено, что внесение корректировок на исходной основе также может принести значительные выгоды с меньшими затратами и большей безопасностью.
В новом дизайне объем кода, который необходимо реализовать, не особенно велик, и сам он представляет собой новую абстракцию, построенную поверх существующего фреймворка. Существующие фреймворки React-IMVC и React-Native продолжают функционировать, только с улучшениями уровня Model и переводом управления репозиторием git в режим Monorepo.
В процессе фактического использования этого режима есть еще много деталей, которые необходимо преодолеть.
Например, такие инструменты, как Webpack/Babel/TypeScript/Node.js/NPM, поддерживают и обрабатывают программные ссылки по-разному.Координация программных ссылок для обеспечения их нормального поведения в каждой среде требует решения многих проблем совместимости.
Например, создание, выпуск и управление ветвями нескольких проектов в репозитории Git — все это новые задачи, с которыми необходимо столкнуться.
7. Перспективы
В настоящее время мы находимся на первом этапе, отделяя слой модели и максимизируя его функции.
На втором этапе мы наложим слой View:
1) Контейнер-Компонент;
2) атом-компонент/атом-элемент;
Другие цели рендеринга, такие как React-Native, React-DOM и даже React-?, предоставляют некоторый Atom-Component или Atom-Element. Например, div/span/h1 в React-DOM, View/Text/Image в React-Native и т. д. Проблема их объединения на уровне атома уже обсуждалась ранее и здесь повторяться не будет.
Мы можем сохранить различия на уровне Atom, чтобы максимизировать возможности каждой цели рендеринга, но сделать некоторую унификацию на уровне абстракции Container.
Как показано на рисунке выше, мы инкапсулируем useComponents через useContext React, внедряем различные реализации компонентов Banner/Calendar на разных платформах, а затем связываем их с состоянием/действиями в модели.
Затем значительная часть кода на уровне представления, например, структура стека компонентов, привязка состояний, привязка событий и т. д., может быть извлечена и повторно использована на нескольких концах. Когда каждая сторона стартует, достаточно внедрить разные реализации компонента. Таким образом, гибкость и свобода базовой реализации сохраняются, а стабильность и непротиворечивость абстракции верхнего уровня достигаются.
Когда мы продолжаем продвигать этот процесс сверху вниз, извлекая все многократно используемые абстракции до тех пор, пока не будут сглажены все основные различия, это эквивалентно реализации кросс-платформенного фреймворка, подобного Flutter. Но нам не нужно начинать строить с нуля, как Flutter, и только после определенной степени завершения мы можем начать играть практическую ценность. Мы строим на существующем фундаменте, принося пользу на каждом этапе пути. И когда Flutter станет более зрелым, мы сможем заменить нижний слой рендерингом Flutter, сохранив при этом абстракцию верхнего слоя.
Таким образом, это подход, который не только имеет дело с текущим затруднительным положением, но и учитывает будущее развитие.
Суммировать
После этого кросс-конечного решения у нас появилось более четкое понимание того, как организован код.
Узнайте больше, чем раньше, какой код следует поместить на уровень модели, какой код следует поместить на уровень представления, какой код можно использовать повторно, какой должен отличаться, какие проблемы решаются средой выполнения, а какие проблемы на самом деле решаются. инженерные проблемы, решаемые путем настройки каталогов и репозиториев git, совместной работы и многого другого.
Когда мы принудительно нивелировали базовые различия, мы обнаружили, что доступных возможностей становилось все меньше и меньше.
Когда мы помещаем то, что должно быть в слой Model, и помещаем это в слой View, мы теряем долгосрочную ценность слоя Model.
Когда мы помещаем инженерные проблемы в среды выполнения для решения, наши среды становятся все более и более раздутыми и работают все медленнее и медленнее.
Мы решили сохранить базовые различия и заменить большую и всеобъемлющую среду выполнения несколькими более легкими средами выполнения.
Создавая абстракцию верхнего уровня, мы объединяем более стабильные части уровня модели и уровня представления, которые имеют долгосрочную ценность, и разделяем их между несколькими проектами.
Таким образом, на каждом уровне у нас есть возможность извлечь максимальную пользу без ущерба для совместимости.
Выше мы в общих чертах описали, как дизайн нашей интерфейсной архитектуры перешел от Backbone.js к Pure-Model + Monorepo + GraphQL-BFF + React-Native/React-IMVC, и представили, с чем мы сталкивались на каждом этапе, вопросы, мысли и финал. выбор.
Они могут не подходить для всех проектов и команд, но я надеюсь, что они могут вдохновить вас или дать пищу для размышлений.