Код можно посмотреть здесь: 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 в своем браузере.

Спасибо за чтение. Я надеюсь, что вы нашли это полезным для ваших проектов. Наслаждаться!