Формируйте решения для сложных сценариев

React.js
Формируйте решения для сложных сценариев

Источник изображения:unsplash.com/

Автор этой статьи: Дун Цзяньхуа

1. Предпосылки

Бизнес-сценарии Cloud Music B-терминала, бизнес-терминал B длиннее, чем жизненный цикл бизнес-продукта C-терминала, и больше ориентирован на сцену сцены. Много раз разработка B-терминала представляла собой код перед копированием, что добавляло много повторений и утомительную рабочую нагрузку.

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

по сравнению с традиционнымAnt DesignМетод разработки формы развития, мы считаем, что существуют следующие проблемы:

  1. Во-первых, код нельзя сериализовать, а это более привычно некоторым не фронтенд разработчикам.JSONспособ описать форму, потому что достаточно простой
  2. Проверка формы не сочетается с состоянием проверки
  3. onChangeРеализованный метод компоновки будет сложно поддерживать в сложных ситуациях компоновки, и легко сгенерировать много логики связанного списка.
  4. Формы имеют много взаимоисключающих состояний для организации, и мы хотим, чтобы пользователи могли легко переключаться между этими состояниями.
  5. Для некоторых распространенных и распространенных сценариев, таких как списки форм, также можно извлечь набор возможных решений.

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

Во внешнем мире есть и более зрелые формы, такие как:Formliy,FormRender. Несмотря на то, что некоторые из вышеперечисленных пунктов были решены, это все еще недостаточно полно.styleстроить планы.

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

2. Технические решения

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

2.1 Дизайн схемы

Схема формы основана наAnt DesignразработаноJSONРасположенная схема, но неJSON Schema, многие аутсайдеры основаны наJSON SchemaСхема комплектации, собственно, тоже рассматривалась, ноJSON SchemaНемного неудобно писать, так что даJSON SchemaПреобразование доступно только как дополнительная возможность.

Случай показан в следующем коде, нужно только настроить самое простое поле формыkey,typeа такжеui.labelПросто хорошо:

const schema = [
    {
        "key": "name",
        "type": "Input",
        "ui": {
            "label": "姓名"
        }
    },
    {
        "key": "age",
        "type": "InputNumber",
        "ui": {
            "label": "年龄"
        },
        "props": {
            "placeholder": "请输入年龄"
        }
    },
    {
        "key": "gender",
        "type": "Radio",
        "value": "male",
        "ui": {
            "label": "性别"    
        },
        "options": [
            {
                "name": "男",
                "value": "male"
            },
            {
                "name": "女",
                "value": "female"
            }
        ]
    }
];

export default function () {
    const formRef = useRef(null);

    const onSubmit = () => {
        formRef.current.submit().then((data: any) => {
            console.log(data);
        });
    };

    const onReset = () => {
        formRef.current.reset();
    };

    return (
        <>
            <XForm
                ref={formRef}
                schema={schema}
                labelCol={{ span: 6 }}
                wrapperCol={{ span: 12 }}
            />
            <div>
                <Button type="primary" onClick={onSubmit}>提交</Button>
                <Button onClick={onReset}>重置</Button>
            </div>
        </>
    );
}

Поскольку схема основана наAnt DesignизFormкомпоненты спроектированы таким образом, чтобы сохранитьAnt Designнекоторые функции, разработанныеuiа такжеpropsДва поля соответствуютForm.Itemизpropsи компонентыprops. даже если последующиеAnt DesignФормы добавляют определенные функции или возможности, и эта схема форм также может поддерживаться без проблем.

2.1.1 Метод проверки

Так как форма основана наAnt Designреализована, то проверка также использует свою библиотеку классов проверкиasync-validator, эта библиотека классов является относительно зрелой и мощной и может проверятьArrayа такжеObjectи другие типы данных глубокого уровня для удовлетворения потребностей комплексной проверки, поэтому мы вносим коррективы непосредственно на основе этой библиотеки.

пройти черезrulesполя для настройки, кромеasync-validatorВ дополнение к уже имеющимся функциям есть дополнительныеstatus(проверить статус) иtriggerПеречисление (условие срабатывания) выглядит следующим образом:

  • статус: проверить статус
    • ошибка (по умолчанию): ошибка
    • предупреждение: предупреждение
  • триггер: условие триггера
    • submit (по умолчанию): срабатывает при отправке
    • изменение: инициировать суждение при изменении значения
    • размытие: инициировать суждение, когда фокус теряется

Основное использование заключается в следующем:

{
    "key": "name",
    "type": "Input",
    "ui": {
        "label": "姓名"
    },
    "rules": [
        {
            "required": true,
            "message": "姓名必填",
            "trigger": "blur",
            "status": "error"
        }
    ]
}

2.1.2 Связь

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

пройти черезlistenersполя настроены, оформленыwatch(монитор),condition(условие),set(Настройки) Комбинация трех полей реализует функцию связи.

watchЗапишите поля, которые необходимо отслеживать, и когда в отслеживаемых полях произойдут какие-либо изменения, он сработает.conditionУсловное суждение, только когда условное суждение будет вынесено, оно будет активированоsetнастраивать.

[
    {
        "key": "name",
        "type": "Input"
    },
    {
        "key": "gender",
        "type": "Radio",
        "value": "male",
        "options": [
            {
                "name": "男",
                "value": "male"
            },
            {
                "name": "女",
                "value": "female"
            }
        ],
        "listeners": [
            {
                "watch": [ "name" ],
                "condition": "name.value === 'Marry'",
                "set": {
                    "value": "female"
                }
            }
        ]
    }
]

В приведенном выше примере, когда имя Marry, пол по умолчанию настроен на женский.

2.1.3 Статус формы

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

  • статус: статус
    • редактировать (по умолчанию): редактировать
    • отключено: отключено
    • Предварительный просмотр: превью
    • скрыто: скрыто

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

Он используется следующим образом:

[
    {
        "key": "edit",
        "type": "Input",
        "status": "edit",
        "value": "编辑",
        "ui": {
            "label": "编辑"
        }
    },
    {
        "key": "disabled",
        "type": "Input",
        "status": "disabled",
        "value": "禁用",
        "ui": {
            "label": "禁用"
        }
    },
    {
        "key": "preview",
        "type": "Input",
        "status": "preview",
        "value": "预览",
        "ui": {
            "label": "预览"
        }
    },
	{
		"key": "hidden",
        "type": "Input",
        "status": "hidden",
        "value": "隐藏",
        "ui": {
            "label": "隐藏"
        }
	}
]

Схема эффекта выглядит следующим образом:

2.1.4 Настройки параметров

Многие избранные компоненты используютoptionsПоле задает параметры, а параметры иногда получают через асинхронный интерфейс. Учитывая ситуацию с асинхронным интерфейсом, разработаны 4 набора схем:

  1. optionsдляArrayСлучай
{
    "key": "type",
    "type": "Select",
    "options": [
        {
            "name": "蔬菜",
            "value": "vegetables"
        },
        {
            "name": "水果",
            "value": "fruit"
        }
    ]
}
  1. optionsдляstringслучай, ссылка на интерфейс
{
    "key": "type",
    "type": "Select",
    "options": "//api.test.com/getList"
}
  1. optionsдляobjectСлучай,actionдля ссылки на интерфейс,namePropertyнастроитьnameполе,valuePropertyнастроитьvalueполе,pathЧтобы получить путь к параметрам,watchНастройте поле прослушивания
{
    "key": "type",
    "type": "Select",
    "options": {
        "action": "//api.test.com/getList?name=${name.value}",
        "nameProperty": "label",
        "valueProperty": "value",
        "path": "data.list",
        "watch": [ "name" ]
    }
}
  1. actionдляfunctionСлучай
{
    "key": "type",
    "type": "Select",
    "options": {
        "action": (field, form) => {
            return fetch('//api.test.com/getList')
                .then(res => res.json());
        },
        "watch": [ "name" ]
    }
}

2.1.5 Список форм

Список форм представляет собой составной тип формы, который обычно имеетTableа такжеCardДва сценария с функциями добавления и удаления.

Значения формы этого типаArrayвозвращается в виде , поэтому конструкцияArrayкомпоненты, согласноprops.typeправильноTableа такжеCardФорма переключается (похоже, что это не так),childrenДля настройки подчиненных форм используйте следующие методы:

{
    "key": "array",
    "type": "Array",
    "ui": {
        "label": "表单列表"
    },
    "props": {
        "type": "Card"
    },
    "children": [
        {
            "key": "name",
            "type": "Input",
            "ui": {
                "label": "姓名"
            }
        },
        {
            "key": "age",
            "type": "InputNumber",
            "ui": {
                "label": "年龄"
            }
        },
        {
            "key": "gender",
            "type": "Radio",
            "ui": {
                "label": "性别"
            },
            "options": [
                {
                    "name": "男",
                    "value": "male"
                },
                {
                    "name": "女",
                    "value": "female"
                }
            ]
        }
    ]
}

Схема эффекта выглядит следующим образом:

2.2 Архитектура фреймворка

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

Переход между основным слоем и слоем рендерингаSub/Pubспособ общения, уровень рендеринга прослушивает серию, определенную базовым уровнемEventСобытия вносят коррективы в интерфейс.

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

  1. Обмен данными и состоянием между полями аспекта
  2. Благодаря управлению событиями количество визуализаций может быть разумно оптимизировано для повышения производительности.
  3. Способен адаптироваться к нескольким платформам, нужно только повторно использовать набор кода основного уровня.

Основной слой состоит в основном изForm,Field,ListenerManager,Validator,optionManagerОн состоит из нескольких частей, как показано на рисунке ниже:

вFormЭто прототип формы, который несет в себе многоFieldПолевой прототип, авторListenerManagerУнифицированное управление функциями связи,FieldподValidatorа такжеOptionManagerУправление контрольными суммами отдельноoptionsОпционная функция

2.2.1 Проверка реализации

главным образом черезasync-validatorБиблиотека классов реализована, но она по-прежнему не может соответствовать условиям мультипроверки состояния и мультитриггерности, поэтому на ее основе сделаны некоторые расширения, и она упакована вValidatorДобрый.

ValidatorединственныйValidator.validateметод, передаваяtriggerпараметры, инстанцированиеValidatorбудет анализироватьrulesполе, согласноtriggerклассифицировать и создавать соответствующиеasync-validatorпример.

2.2.2 Реализация связи

ListenerManagerимеютListenerManager.addМетоды иListenerManager.triggerметоды соответственно для разбора и добавленияlistenersполя иFieldЭффект связи срабатывает при изменении поля.

Конкретный процесс заключается в инициализацииField, будуlistenersполе пройденоlistenerManager.addметод разбора информации, согласноwatchсерединаkeyзначение сортируется и сохраняется в нем, когдаFieldКогда информация изменится, она пройдетListenerManager.triggerтриггерная связь, суждениеconditionВыполняется ли условие, если да, то триггерsetсодержание.

2.2.3 Реализация списка форм

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

При нажатии кнопки «Добавить» будетchildrenкоторый предоставилSchemaшаблон для созданияXFormПример:

2.2.4 Реализация макета

КромеAnt DesignТри метода макета (горизонтальный, вертикальный, встроенный), предоставляемые формой, также должны предоставлять более гибкий метод макета для удовлетворения более сложных ситуаций.

Макет — настоящая головная боль, особенноSchemaв подобномJSONВнедрение сложных макетов под структуру может легко привести кSchemaУровень вложенности глубокий, чего мы не хотим видеть.

Исходное решение реализовано через макет сетки, установивFormизrow.countилиcol.countПараметры вычисляют количество строк и столбцов сетки, а затем распределяют поля.Этот метод подходит только для случая, когда количество каждой строки и столбца одинаково, но этот метод трудно удовлетворить ситуацию, когда число каждой строки и столбца несовместимы:

Итак, редизайнui.groupnameполя, то же, чтоgroupnameполя будут заменены наdivзавернутый, иdivизclassNameкоторыйgroupname, пользователи могут писать свои собственные стили для создания сложных макетов.Хотя такая схема проста, она практична.

3. Детальный дизайн

3.1 Игнорировать определенные значения полей

Некоторые сценарии следует игнорироватьstatusдляhiddenЗначение поля, поэтому спроектируйтеignoreValuesПоле, конфигурация поля имеет следующие ситуации:

  • hidden: игнорировать случаи, когда состояние скрыто
  • предварительный просмотр: игнорировать статус предварительного просмотра
  • отключено: игнорировать состояние отключено
  • null: игнорировать случай, когда значение равно null
  • undefined: игнорировать случай, когда значение не определено
  • falseLike: игнорирует случаи, когда значение == false

по конфигурацииignoreValuesполе, возвращенное после отправкиvaluesСоответствующие поля игнорируются:

<XForm schema={schema} ignoreValues={['hidden', 'null']}/>

3.2 Деструктуризация и реорганизация полей

Деструктуризация поля относится к разбиению значения поля на несколько полей, а реорганизация поля — к объединению нескольких полей в одно поле Конкретные функции этого блока еще не реализованы, но есть предварительные идеи.

Примеры деструктуризации поля следующие, в основном черезkeyРазделите поле и, наконец, вернитесьvaluesВключатьstartTimeа такжеendTimeДва поля:

{
    "key": "[startTime, endTime]",
    "type": "RangePicker",
    "ui": {
        "label": "时间选择"
    }
}

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

{
    "key": "time",
    "type": "Combine",
    "ui": {
        "label": "时间选择"
    },
    "props": {
        "shape": "{startTime, endTime, type}"
    },
    "children": [
        {
            "key": "startTime",
            "type": "DatePicker"
        },
        {
            "key": "endTime",
            "type": "DatePicker"
        },
        {
            "key": "type",
            "type": "Select",
            "options": [
                {
                    "name": "发行时间",
                    "value": "publishTime"
                },
                {
                    "name": "上线时间",
                    "value": "onlineTime"
                }
            ]
        }
    ]
}

4. Конец

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

5. Сопутствующая информация

Эта статья была опубликована сКоманда внешнего интерфейса NetEase Cloud Music, Любое несанкционированное воспроизведение статьи запрещено. Мы набираем front-end, iOS и Android круглый год.Если вы готовы сменить работу и любите облачную музыку, присоединяйтесь к нам на grp.music-fe(at)corp.netease.com!