Код можно посмотреть здесь: https://github.com/mattpaletta/branching-forms-example
В настоящее время я работаю над проектом, который требует сложных веб-форм. В частности, для этого требуется многостраничная форма, которая принимает ввод от пользователя на каждом этапе, но, в отличие от многих многостраничных онлайн-форм, следующий экран отличается в зависимости от того, что пользователь выбрал на последнем экране. Отсюда и термин ветвящиеся веб-формы.
Я использую React для этого проекта и структурировал свой код таким образом, чтобы следовать архитектуре MVC. Это уже имело огромное значение для меня с точки зрения управления и тестирования большого веб-приложения в качестве одного разработчика. Вот отличный ресурс о MVC, предназначенный для разработки под iOS, но те же принципы применимы и к приложению React: https://learnappmaking.com/model-view-controller-mvc-swift/
Моя первоначальная попытка создать эту разветвленную форму состояла в том, чтобы поместить все мое состояние для всей формы в родительский контроллер, но это быстро превратилось в неуправляемый клубок состояний и операторов if. Валовой.
Я хотел бы представить архитектуру программного обеспечения, к которой я пришел и которой я очень доволен. Пример, который я буду использовать, — это форма, которая поможет пользователю решить, какого питомца ему следует завести, на основе ряда вопросов. Некоторые варианты включают: есть ли у них двор, есть ли у них аллергия и т. д.
Ниже представлена схема решений через форму. Я понимаю, что это очень упрощенный пример, но я выбрал его, потому что он достаточно прост для понимания и не слишком сложен, поэтому мы можем сосредоточиться на структуре кода, поддерживающей это.
Теперь давайте перейдем к коду!
Глубокое погружение в код
Код разбит на две части: библиотеку (в pet_chooser/src/lib
) и приложение (в pet_chooser/src/components
). Давайте сначала рассмотрим структуру приложения, так как это поможет сформулировать сценарий использования архитектуры библиотеки.
Применение
Точка входа находится в pet_chooser/src/App.tsx
. Ничего особенного здесь не происходит, он настраивает наше представление верхнего уровня и создает наш FrienshipFormController
.
Сам FrienshipFormController
довольно прост. Он оборачивает GenericForm
(из библиотеки) и определяет первый экран формы как HomeSelection
, который спрашивает, живете ли вы в доме или в квартире.
Библиотека
Библиотека относительно проста в реализации, но имеет некоторые тонкости. Есть только 2 важных класса: BaseStep
и GenericForm
.
GenericForm
компонент, который мы настроили в нашем FrienshipFormController
, отвечает за сохранение текущего отображаемого экрана, а также за отображение и управление кнопками «Далее» и «Назад» для перемещения между экранами.
Другой, более интересный компонент — BaseStep
. Ниже представлен интерфейс, в основном псевдокод машинописного текста. Некоторые детали реализации были удалены для простоты:
class BaseStep { private readonly update_callback: CallableFunction | undefined; private readonly prevStep: BaseStep | undefined; protected state: Map<string, any>; constructor(prevStep, update_callback) : none forceUpdate() : none setStepState(key: string, value: any) : none getStepStateOr(key: string, or: any) : any getStepState(key: string) : any | undefined recalculateErrors() : number shouldShowNextButtons() : booelean showShowBackButton() : boolean getScreen() : HTMLElement getNextScreen() : BaseStep | undefined getPreviousScreen() : BaseStep | undefined }
Итак, теперь, когда мы обрисовали в общих чертах строительные блоки, мы можем поговорить о том, как собрать их вместе.
По сути, этот класс BaseStep
позволяет нам определить каждый экран как класс, который реализует BaseStep
. Каждый экран имеет свою собственную логику сохранения своего локального состояния, рендеринга, а также расчета наличия ошибок на экране и того, на какой экран следует перейти дальше.
Каждый BaseStep
получает указатель на предыдущий экран, prevStep
. Это создает связанный список экранов. Эта абстракция позволяет каждому экрану определять следующий экран на основе его текущего и предыдущего состояний экранов.
BaseStep
может хранить локальное состояние как частные свойства, установленные в его конструкторе. Любое состояние, совместно используемое с другими экранами, можно установить и прочитать с помощью методов setStepState
и getStepState
. В getStepState
текущий экран смотрит на свое собственное состояние, и если у него есть значение, он возвращает значение в карте состояний. Если нет, то он будет следовать указателю предыдущего экрана и рекурсивно вызывать тот же метод на предыдущем экране. Код для getStepState
показан ниже:
getStepState(key: string): undefined | any { if (this.state.has(key)) { return this.state.get(key); } else if (this.prevStep !== undefined) { return this.prevStep.getStepState(key); } else { return undefined; } }
Когда класс обновляет свое состояние, он вызывает forceUpdate()
, что заставляет GenericForm
перерисовывать все дочерние элементы, в данном случае текущий экран. При этом GenericForm
запрашивает у экрана recalculate_errors()
, что реализует экран. Этот метод возвращает количество ошибок, которые экран имеет в данный момент, прежде чем он будет готов перейти к следующему экрану. Как только это число достигает 0, кнопка «Далее» активируется в GenericForm
. Когда пользователь нажимает «Далее», на текущем экране вызывается getNextScreen()
. Ниже приведен пример реализации, который решает, какой следующий экран показывать, в зависимости от текущего состояния экрана.
getNextScreen(): BaseStep | undefined { if (this.getStepState(PetFormSources.HOUSING.OPTION) === "House") { return new HasYardSelectionStep(this); } else { return new HasAllergiesStep(this); } }
Указатель this
является ключом к созданию связанного списка. Поскольку каждый элемент реализует BaseStep
, указатель this
(относящийся к текущему экрану) передается как предыдущий экран следующего экрана.
Запуск примера
На этом пошаговое руководство по коду завершено, теперь давайте запустим пример, чтобы вы могли поиграть с ним и увидеть, как он работает в действии.
Клонируйте репозиторий кода, перейдите в папку pet_chooser
и установите зависимости npm.
git clone https://github.com/mattpaletta/branching-forms-example cd branching-forms-example/pet_chooser npm install
Наконец, запустите npm start
.
Откройте localhost:3000
в своем браузере.
Спасибо за чтение. Я надеюсь, что вы нашли это полезным для ваших проектов. Наслаждаться!