Высокопроизводительные формовочные решения для сложных сценариев

React.js
После 3 лет крещения UForm, разработанный и созданный командой разработчиков платформы цепочки поставок Ali, наконец-то выпущен! 🎉🎉🎉
UForm, омоним Your Form , означает, что это решение Form, которое вам нужно.
Высокая производительность, высокая эффективность, масштабируемость — три основные характеристики UForm.
Пожалуйста, нажмите, чтобы просмотреть весь документalibaba.github.io/uform

Источник

Я до сих пор помню, что 4 года назад, когда я впервые присоединился к Tmall, я получил запрос на миддл, бэкенд и фронтенд, это была страница для создания купонов на полные скидки в супермаркете Tmall, в то время React только начинал стать популярным. Я был молод и решителен. Чтобы использовать React для разработки, это простая входная страница CRUD, а для выполнения требований используется редукс-архитектура. Однако на завершение разработки у меня ушло в общей сложности 15 дней. Конечно, не исключено, что при первом контакте с редуксом кривая обучения была относительно крутой, и что более важно, бизнес-логика этой страницы очень сложная, самые сложные части в основном это:

  • Лестничное правило, форма списка
  • Существует динамическая связь между полями одного уровня в каждом слое лестницы.Например, если поле A изменяется, оно будет управлять отображением и скрытием поля B.
  • Существует проверка связи между полями одного уровня в каждом слое лестницы, например: значение поля B должно быть больше или равно значению поля A
  • Поля между уровнями имеют проверку связи.Например, значение поля A во второй лестнице больше, чем значение поля B в первой лестнице.

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

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

вопрос

1. Проблемы с производительностью

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

cosnt App = ()=>{
  const [value,setState] = useState()
  return (
      <div>
          <ComponentA>
           <ComponentB value={value}/>
          </ComponentA>
          <button onClick={()=>setState('changed')}>改变value</button>
      </div>
  )
}

Если элемент управления shouldComponentUpdate выполняется для ComponentA, пока нет изменений в свойствах ComponentA, запуск повторного рендеринга компонента App через setState не вызовет повторный рендеринг компонента ComponentB и не вызовет ComponentB. для получения последнего значения свойства.

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

Кроме того, в последней системе React Hooks React не может быть обработана функция shouldComponentUpdate. Существует только один API React.memo, но он делает лишь поверхностное сравнение свойств. С этой точки зрения кажется, что официальные лица React сами оптимизировали способ для дальнейшая производительность.Это кажется заблокированным.На самом деле, основная причина в том, что React официально уважает неизменяемые данные, и все операции с данными должны строго следовать неизменяемому способу выполнения операций с данными, но для общих компонентов, чтобы обеспечить надежность компонентов, как правило, не предполагают, что пользователи передают неизменяемые атрибуты.В конце концов, вы хотите настаивать на одностороннем потоке данных для управления всеми данными? Проблемы с производительностью уже на горизонте.

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

2. Проблемы с ремонтопригодностью кода

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

  • Стиль кода соответствует стилю командного кода, который может быть ограничен eslint.
  • Код модульный, не должен быть файлом, содержащим всю бизнес-логику, необходимо делать физическое разбиение
  • Логика является иерархически, сервис - это слой, просмотр - это слой, основной бизнес логический слой, может ссылаться на MV *, каждый слой не должен быть легирован другими функциональными слоями
  • Представление состоит из компонентов, которые абстрагируют и инкапсулируют общую логику взаимодействия представления слой за слоем, еще больше упрощая сложность основного представления.

Давайте взглянем на традиционные решения для форм, такие как решение формы Ant Desgin. Ниже приведены его характеристики использования:

  • Требуется, чтобы бизнес-компоненты, использующие форму, использовались единообразно.Form.create()обертка, чтобы обернуть его
  • Получите метод экземпляра формы через реквизиты, напримерgetFieldDecorator,пройти черезgetFieldDecoratorПереупаковка полей формы для сбора и проверки данных
  • onChange Handlerв, черезthis.props.form.setFieldsValue

onChange Handler, и, наконец, привести к тому, что бизнес-компоненты станут очень раздутыми. Для начинающих, написание большого количества обработчиков onChange, скорее всего, напишет анонимные функции непосредственно в jsx. Затем это также приведет к тому, что слой jsx станет очень грязным и грязным, поэтому , логика обработки событий должна быть строго изолирована от слоя jsx, иначе будет беспокоить ремонтопригодность кода.Конечно, для простых сценариев нет проблем с использованием Antd Form, но Antd Form по-прежнему использует одиночное состояние управляется способом потока данных, то есть любое изменение поля приведет к полной визуализации компонента Аналогично, Fusion Next Form также имеет ту же проблему.

3. Вопросы эффективности развития формы

Когда дело доходит до эффективности R & D, некоторые, насколько это возможно, позволяют пользователям писать меньше дублирования кода, или если вы используете Antd Form Fusion Next Form, вы обязательно найдете свои внутренние компоненты везде Компоненты FormItem, вездеonChange HandlerГде угодно{...formItemLayout}, эти повторяющиеся и неэффективные коды не должны существовать.

4. Проблемы с внутренними данными

В некоторых сценариях наша страница формы очень динамична. Внешний интерфейс не может понять, существуют ли некоторые поля или нет. Серверная часть создает таблицу и вручную вводит информацию о полях пользователями с различными профессиональными атрибутами, такими как корзина для покупок в электронной коммерции. . Страница формы, страница заказа транзакции, системе нужны возможности тысяч людей, поэтому внешний интерфейс должен иметь возможность динамического рендеринга формы. Ни Antd Form, ни Fusion Next Form изначально не поддерживают такой динамический рендеринг. вы можете только инкапсулировать слой механизма динамического рендеринга в верхнем уровне, так что рендеринг должен управляться на основе определенного протокола JSON.Я видел много-много подобных решений для динамического рендеринга форм, и протоколы JSON, определенные ими, все очень кастомизировано, или недостаточно стандартно, а некоторые используются вообще без учета полноты, что в итоге приводит к очень сложной бизнес-логике front-end и back-end. Поэтому протокол динамического рендеринга формы лучше всего сделать стандартным и полным, иначе будет сложно заполнить следующие ямы.

проводить исследования

Из вышеуказанных нескольких вопросов мы видим, хотите лучшее сцену реагирования на письменной странице формы действительно очень сложна, не на самом деле не подходят для записи страницы React Project?

После долгих поисков и исследований различных решений для форм я, наконец, нашел решение для формы, которое может фундаментально решить проблему производительности, а именно:final-form, этот компонент является исходнымredux-formНовое решение формы, переработанное автором , Идея этого решения очень ясна, то есть каждое поле управляет своим состоянием, делает свою отрисовку и обновление и распределяет управление состоянием, что полностью противоречит -концепция потока данных избыточной формы, но преимущества проявились сразу же, общее время рендеринга формы было значительно сокращено, а нагрузка на ЦП при рендеринге React также была значительно снижена.Поэтому окончательная форма, как и его имя, закончилась проблема производительности формы.

В то же время, с точки зрения удобства сопровождения кода, final-form также имеет свое яркое пятно, то есть абстрагирует логику связывания полей формы в независимой форме.calculateТаким образом, бизнес-компоненты не будут сильно раздуваться. Тем более, что расширяемый авторский дизайн final-form тоже очень понятен.К тому же final-form это готовое решение.Это оболочка.Различные библиотеки компонентов можно комбинировать и использовать в виде рендера В заключение, окончательное решение формы решает большинство проблем в области формы.

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

  1. Связь не может быть записана в одном месте, и калькулятор сам по себе не может справиться с привязкой состояния.Например, изменение значения поля A будет контролировать отключенное состояние поля B.Привязка состояния должна сочетаться с Полевая подписка уровня jsx. Пользователям необходимо постоянно переключать метод написания для развития опыта. Хуже, например:код sandbox.io/yes/solve 94 мой дом 9…
  2. Вложенные структуры данных требуют ручного соединения путей полей, таких какcodesandbox.io/s/8z5jm6x80
  3. Механизм связи внутри и снаружи компонента слишком хакерский, например, вызов функции Submit извне.code sandbox.IO/yes/1 не содержит 7 человек внутри...
  4. Обновление состояния поля внутри формы не может точно контролироваться вне компонента, если только не используется механизм одностороннего потока данных глобального повторного рендеринга.
  5. Динамический рендеринг формы не поддерживается, и на верхнем уровне необходимо установить механизм динамического рендеринга.

Исследуйте и внедряйте инновации

  • Побочные эффекты управляются независимо, в основном для логики управления состоянием поля формы. Преимущество независимости заключается в улучшении ремонтопригодности слоя представления. В то же время он унифицирован и сведен к одному месту для обслуживания, которое более удобным для пользователя.
  • Автоматическое объединение путей вложенных структур данных
  • Более элегантный способ общения внутри и снаружи компонента, а снаружи также можно точно контролировать обновление поля
  • На основе стандартной структуры данных JSON Schema для расширения создайте механизм динамического рендеринга форм.

Наконец, мы можем получить прототип решения:JSON Schema + Полевое распределенное управление+ Схема управления связью для сложных общих компонентов

Схема JSON описывает структуру данных формы.

Зачем использовать схему JSON? У нас есть несколько соображений:

  • Стандартизированные протоколы, которые являются простыми для понимания общими протоколами как для внешнего, так и для внутреннего интерфейса.
  • Схема JSON больше фокусируется на описании данных, а не на описании пользовательского интерфейса. Из-за формы это ввод данных. Мы надеемся, что то, что волнует пользователей,Больше данных, а не пользовательский интерфейс
  • Схема JSON может использоваться в различных сценариях, управляемых данными, таких как конфигуратор компонентов в механизме визуального построения и т. д.

Что такое JSchema?

<Field type="Object" name="aaa">
   <Field type="string" name="bbb"/>
   <Field type="array" name="ccc">
      <Field type="object">
          <Field type="string" name="ddd"/>
       </Field> 
   </Field>
</Field>
​
//========转换后===========
{
   "type":"object",
    "properties":{
        "aaa":{
            "type":"object",
            "properties":{
                "bbb":{
                    "type":"string"
                },
                "ccc":{
                    "type":"array",
                    "items":{
                        "type":"object",
                        "properties":{
                            "ddd":{
                                "type":"string"
                            }
                        }
                    }
                }
            }
        }
    }
    
}

Расширения свойств схемы JSON

Поскольку схема JSON изначально использовалась для описания данных, если использоваться непосредственно перед фронтом, вы потеряете много метаданных, связанных с пользовательским интерфейсом, то метаданные должны быть, как это описать? Решения Mozilla разработаны абстрактным соглашением под названием «Уи» схема для описания формы специализированной структуры интерфейса UI, вы можете увидетьGitHub.com/Mozilla — Перселл…. Кажется, что это отделяет пользовательский интерфейс от данных, что очень ясно.Однако, если мы посмотрим на эту проблему с точки зрения компонентизации, описание данных поля формы должно быть подмножеством описания компонента поля формы, и два интегрированы в один Это больше соответствует человеческому мышлению Чтобы не загрязнять итерацию обновления исходного протокола json-schema, мы можем добавить атрибуты x- * в описание данных, чтобы и описание данных, и пользовательский интерфейс описание может быть принято во внимание.На уровне кода пользователи также могут Не нужно делать настройку в двух местах, и будет удобнее устранять неполадки.

Распределенное управление статусом поля

Если вы хотите понять, что такое распределенное управление состоянием поля, вы должны сначала понять, что такое односторонний поток данных.Помните, когда React впервые стал популярным, все говорили об одностороннем потоке данных, как и текущая концепция React Hooks. ., мне также потребовалось много времени, чтобы понять, что такое односторонний поток данных.

По сути, односторонний поток данных в двух словах таков:Синхронизация данных управляется перерисовкой корневого компонента, а перерисовка дочернего компонента контролируется корневым компонентом.

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

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

Схема управления связью для сложных общих компонентов

Для схемы управления связью сложных общих компонентов использование механизма одностороннего потока данных для управления проблемами производительности очень серьезно, поэтому мы можем только думать о том, существуют ли другие схемы, на самом деле схемы нет, ref — это очень распространенная коммуникация Однако очевидны и его проблемы.Например, его легко перехватить HOC.Хоть и есть forwardRef API, но его все равно неудобно писать, а также повышает уровень компонентов и улучшает сложность компоненты.

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

class MyComponent extends React.Component {
    constructor(props){
        super(props)
        this.state = {
            data:{}
        }
        if(props.actions){
            props.actions.getData = ()=>{
                return this.state.data
            }
            props.actions.setData = (data)=>{
                this.setState({data})
            }
        }
    }
}

Это самый примитивный ref-подобный API, при использовании компонентов нам нужно только

const actions = {}
<div>
   <MyComponent actions={actions} />
    <Button onClick={()=>{
            actions.setData('hello world')
    }}>设置状态</Button>
</div>

Таким образом, программа не будет HOC для перехвата риска, частный API будет использоваться и не появится, однако эта программа предназначена для внешней связи - поток данных > внутри, затем внутренний -> обмен внешними потоками данных, если то, что есть Это? Я думал об этом на основе исходного режима атрибута onXXX, выставляя все виды событий в ответ на API в реквизитах компонента, но, таким образом, кажется, что я упоминал ранее, что логика фрагментации приводит к уменьшению проблем с ремонтопригодностью кода, ссылка Редукционные шаблоны проектирования, подчеркивающие его суть: действия по конвергенции, плоская бизнес-логика, сходятся, полимеризуются на редукторе, поэтому нам нужна конвергенция сосуда для полимеризации, чтобы нести бизнес-логику, так что как четкая степень подъема структуры, так и может улучшить ремонтопригодность.

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

class MyComponent extends React.Component {
    constructor(props){
        super(props)
        this.state = {
            data:{}
        }
        if(props.actions){
            props.actions.getData = ()=>{
                return this.state.data
            }
            props.actions.setData = (data)=>{
                this.setState({data})
            }
        }
        if(typeof props.effects === 'function'){
            this.subscribes = {}
            props.effects(this.selector)
        }
    }
    
    selector = (type)=>{
        if (!this.subscribes[type]) {
          subscribes[type] = new Subject() //rxjs的核心API Subject
        }
        return subscribes[type]
    }
    
    dispatch = (type,payload)=>{
        if(this.subscribes[type]){
            subscribes[type].next(payload)
        }
    }
    
    render(){
        return <div>
             {JSON.stringify(this.state.data)}
             <button onClick={()=>dispatch('onClick','clicked')}>点击事件触发</button>
        </div>
    }
}

Итак, когда мы, наконец, используем его, нам нужно только

const actions = {}
const effects = ($)=>{
    $('onClick').subscribe(()=>{
        actions.setData('data changed')
    })
}
<div>
   <MyComponent actions={actions} effects={effects} />
    <Button onClick={()=>{
            actions.setData('hello world')
    }}>设置状态</Button>
</div>

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

const actions = {}
const effects = ($)=>{
    $('onClick').subscribe(()=>{
        actions.setData('data changed')
    })
}
<div>
    <MyComponentA actions={actions} effects={effects} />
    <MyComponentB actions={actions} effects={effects} />
    <MyComponentC actions={actions} effects={effects} />
    <Button onClick={()=>{
            actions.setData('hello world')
    }}>设置状态</Button>
</div>

Что ж, предыдущие — это все прототипы, мы можем еще больше абстрагироваться от этой части логики, и, наконец, она становитсяreact-eva.

атмосферные осадки

Таким образом, три элемента нашего решения формы могут быть изменены на:

JSON Schema(JSchema) + Полевое распределенное управление+ React EVA

Так родился UForm.Он был разработан в строгом соответствии с вышеизложенными идеями.Приглашаем всех попробовать! Если у вас есть какие-либо вопросы, не стесняйтесь жаловаться!

рекламировать

Передняя часть платформы цепочки поставок Ali постоянно набирает ... Добро пожаловать, чтобы отправить свое резюмеzhili.wzl@alibaba-inc.com