Design concept

There are a lot of library methods for code design and rendering, because this is really painful. Everyone wants to find a solution that eliminates repetitive labor, while everyone has the practical needs of thousands of people, which is actually a “cursed problem” : to be easy to use to reduce the amount of configuration, to free customization to increase the amount of configuration. Such a stiff solution is a solution to the problem of, in various business docking platform, in constant groping through trade-off, restrictions, and sacrifice, for one even in use, also support customized solutions, a willing to use our own solution: using JSON configuration to generate the page, page development effort can be reduced, greatly improve efficiency.

Open source address demo address

The minimalist API

<CustomComponent value={this.state.value} onChange={this.onChange} />
Copy the code

The simplest form-render API based on this

<SchemaRender
  schema={schema}
  formData={this.state.value}
  onChange={this.onChange}
/>
Copy the code

The UI for schema to describe forms is essential, and formData is value. This is SchemaRender in its simplest form!

import React, { useState } from 'react';
import { SchemaRender } from "~renderer";

const schema = {
  type: 'object'.properties: {
    string: {
      title: 'String'.type: 'string',},select: {
      title: 'radio'.type: 'string'.enum: ['a'.'b'.'c'].enumNames: ['option 1'.'option 2'.'option 3'],}}};const Demo = () = > {
  const [formData, setFormData] = useState({});
  return (
    <SchemaRender schema={schema} formData={formData} onChange={setFormData} />
  );
};

export default Demo;
Copy the code

A complex scenarioDemo

const schema_json = {
  type: "object".properties: {
    case1: {
      title: "Base control".type: "object".displayType: "column".labelWidth: 110.properties: {
        input: {
          title: "Simple input field".type: "string".displayType: "row".required: true.options: {
            placeholder: "Please enter"}},email: {
          title: "Mailbox entry".description: "Mailbox format Verification".type: "string".displayType: "row".format: "email".required: true.pattern: "^[.a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$"},... };Copy the code

Design ideas

Define type resolution relationships based on custom component types

const widgets = {
  checkbox,
  checkboxes, 
  color,
  date,
  dateRange,
  input
  multipleSelect,
  number,
  radio,
  select,
  slider,
  switch,
  textarea,
  html,
  ...
};
Copy the code

Handle mapping flexibly

const mapping = {
  default: "input".string: "input".boolean: "switch".integer: "number".number: "number".object: "map".html: "html".size: "size".select: "select"."date:dateTime": "date"."string:upload": "upload "string:date":"date","string:dateTime":"date","string:time":"date","string:textarea":"textarea","string:color":"color","range:date":"dateRange","range:dateTime":"dateRange",... };Copy the code

Wrapping custom components with higher-order functions

import React from "react";

// High order component
export default function fetcher(FieldComponent) {
  return class extends React.Component {
    render() {
      return <FieldComponent {. this.props} / >; }}; }Copy the code

Parse the JSON configuration to generate the unit component

import React, { useRef, useMemo, useEffect, useImperativeHandle } from "react";

// Todo: schemaParser parses core configuration
function RenderField({ fields, onChange, ... settings }) {
  const { Field, props } = schemaParser(settings, fields);
  if(! Field) {return null;
  }
  return <Field isRoot {. props} value={settings.data} onChange={onChange} formData={settings.formData} />;
}

/ * * *@param Generated Field * generated based on the Widget@param Customized Field *@param Mapping relationship between type and widgetName */
function FieldRender({
  className = "",
  name = "$Field",
  schema = {},
  formData = {},
  widgets = {},
  FieldUI = DefaultFieldUI,
  fields = {},
  mapping = {},
  onChange = () => {},
  forwardedRef
}) {
  const originWidgets = useRef();
  const generatedFields = useRef({});

  const rootData = useMemo(() = > schemaResolve(schema, formData), [schema, formData]);

  // Data changes are often used, so the first place
  const resetData = (newData, newSchema) = > {
    const _schema = newSchema || schema;
    const _formData = newData || formData;
    const res = schemaResolve(_schema, _formData);
    return new Promise((resolve) = > {
      onChange(res);
      resolve(res);
    });
  };

  useImperativeHandle(forwardedRef, () = > ({
    resetData
  }));

  // All user input calls this function
  const handleChange = (key, val) = > {
    onChange(val);
  };

  const generated = useMemo(() = > {
    let obj = {};
    if(! originWidgets.current) { originWidgets.current = widgets; }Object.keys(widgets).forEach((key) = > {
      const oWidget = originWidgets.current[key];
      const nWidget = widgets[key];
      let gField = generatedFields.current[key];
      if(! gField || oWidget ! == nWidget) {if(oWidget ! == nWidget) { originWidgets.current[key] = nWidget; } gField = asField({ FieldUI,Widget: nWidget });
        generatedFields.current[key] = gField;
      }
      obj[key] = gField;
    });
    returnobj; } []);const settings = {
    className,
    name,
    schema,
    data: rootData,
    formData
  };

  const fieldsProps = {
    // Field generated according to Widget
    generated,
    // Custom Field
    customized: fields,
    // The mapping between field type and widgetName
    mapping
  };

  return <RenderField {. settings} fields={fieldsProps} onChange={handleChange} />;
}

// todo: the outer container is decoupled from the component
const Wrapper = ({ schema, ... args }) = > {
  return <FieldRender schema={combineSchema(schema)} {. args} / >;
};

export default Wrapper;
Copy the code

How can a container support infinite level nesting

import React, { useState } from "react";

// todo: getSubField handles json parsing of subset objects or arrays
function SubComponent((p)  {
  return (
    <>
      {Object.keys(p.value).map((name) => {
        return p.getSubField({
          name,
          value: p.value[name],
          rootValue: p.value,
          onChange(key, val, objValue) {
            letvalue = { ... p.value, [key]: val };//The third argument allows a child control in object to change the value of the entire objectif(objValue) { value = objValue; } p.onChange(p.name, value); }}); })} < / a >); });

export default memo(SubComponent);
Copy the code

To be continued…