Creating a dynamically configurable page

In smallfish you can dynamically configure component libraries using smallfish/dcc by turning on the dcc (Dynamic Configuration Component) configuration.

What is the dynamic configuration component?

dcc is a development mode that describes the local page structure through Schema, provides a matching component data association library, and implements a scene that can dynamically configure the page.

A Schema is like this:

const schema = {
  api: {
    getList: '/api/list',
    getUser: '/api/user',
  },
  root: {
    id: 'antd-pro-layout',
    componentName: 'AntdProLayout',
    props: {
      id,
    },
    children: [
      {
        id: 'basic-table',
        componentName: 'BasicTable',
        props: {},
      },
    ],
  },
};

In fact, it follows:

<AntdProLayout id={id}>
  <BasicTable />
</AntdProLayout>

It's the same, but because abstraction becomes a Schema, it can match the data dynamics, which means that the length of the page can be determined by the data of the server.

Dynamic configuration component

In a page configurable scenario, component associations and data mappings are often difficult to handle when building rich interactive applications.

Generally speaking, similar configurable forms in the industry are generally SchemaBuilder -> Schema -> SchemaRender, in which the information about the relationship between components and data flow is usually described by Schema.

However, this way makes Schema difficult to maintain and complicated to configure. This is because the previous ideas are component partitioning according to the dimensions of the UI component split, but in smallfish/dcc, we divide it by business module.

That is to say, we are separated from the dimension of the UI, from the perspective of product business (data perspective) to divide the components, and separate the business components (Business Components).

But a business module will form a business process with multiple business modules, and this business process is a closed loop. In smallfish/dcc we represent the concept of DataContainer.

Usually a system contains multiple business processes, each of which is independent of each other, but may collect system information uniformly. The outermost layer in smallfish/dcc is GlobalContainer, which is called RenderComponent. Rendering Schema is automatically added and does not require manual processing by the user.

smallfish/dcc solved problems

In summary, smallfish/dcc provides a set of development methods to implement the dcc development model, in which the proposed three-tier component structure can solve the problem of the relationship between management components in dynamic configuration.

  • GlobalContainer: represents a business system that provides globalData at the data level
  • DataContainer: represents a business process, providing storeData at the data level
  • BusinessComponent: Represents a business module that manages itself at the data level.

The underlying relationship is from the bottom of the Schema to the various business components.

Using smallfish/dcc

Rendering by Schema

// enabled in the config/config.js configuration item
{
  dcc: true,
}

and import smallfish/dcc:

import React from 'smallfish/react';
import { DataContainer, RenderComponent } from 'smallfish/dcc';

const schema = {
  ppi: {
    getWife: '',
  },
  root: {
    id: 'parent',
    componentName: 'Parent',
    props: {},
    children: [
      {
        id: 'sun',
        componentName: 'Sun',
        props: {},
      },
      {
        id: 'daughter',
        componentName: 'Daughter',
        props: {},
      },
    ],
  },
};

const Parent = DataContainer(({ children }) => <div>Parent {children}</div>, {
  Car: 'GTR',
  House: 'Chengdu',
});
Parent.displayName = 'Parent';

const Sun = ({ storeData }) => <div>sun has {storeData.car}</div>;
Sun.displayName = 'Sun';

const Cat = ({ name }) => <div>cat's master firstname is {name}</div>;
Cat.displayName = 'Cat';

const Daughter = ({ storeData, GlobalConsumer }) => (
  <div>
    Daughter has {storeData.house}
    <GlobalConsumer>
      {({ globalData }) => <Cat name={globalData.firstname} />}
    </GlobalConsumer>
  </div>
);
Daughter.displayName = 'Daughter';

export default () => (
  <RenderComponent
    schema={schema}
    components={[Parent, Sun, Daughter]}
    globalData={{
      firstname: 'jack',
    }}
  />
);

Associating components with setStoreData and storeData

When a component is wrapped by DataContainer, it represents a business process with the ability to pass data down. Its subcomponents can access business process data via props.storeData and set up business processes via props.setStoreData.

const Parent = DataContainer(({ children }) => <div>Parent {children}</div>, {
  car: 'GTR', // initialized store: { car: 'GTR' }
});
Parent.displayName = 'Parent';

// subcomponents can get { car: 'GTR' } via storeData
const Sun = ({ storeData }) => <div>sun has {storeData.car}</div>;
Sun.displayName = 'Sun';

// subcomponents can also set data via setStoreData
const Sun = ({ storeData, setStoreData }) => {
  // Update car to BMW, then the car read by the parent and child components will change
  useEffect(() => {
    setStoreData({
      car: 'BMW',
    });
  }, []);

  return <div>sun has {storeData.car}</div>;
};
Sun.displayName = 'Sun';

And all of these relationships are decentralized to the component layer, there is no performance in the Schema, this is the core idea of ​​smallfish/dcc: let the business components handle the context of the relationship, but to remain independent to storeData As an input and output interface.

Processing global data with globalData and setGlobalData

A business system may have multiple business processes, each of which is a business component of a DataContainer package, and globalData is a solution to the problem of manipulating data across multiple business process components.

const Parent = DataContainer(
  ({ children, GlobalConsumer }) => (
    <GlobalConsumer>
            
      {({ globalData, setGlobalData }) => (
        <>
          Parent name: {globalData.firstname} has: {children}
        </>
      )}
          
    </GlobalConsumer>
  ),
  {
    car: 'GTR', // initialized globalData: { car: 'GTR' }
  },
);
Parent.displayName = 'Parent';

export default () => (
  <RenderComponent
    schema={schema}
    components={[Parent, Sun, Daughter]}
    globalData={{
      // global data
      Firstname: 'jack',
    }}
  />
);

Setting global configuration via globalConfig

Because RenderComponent is rendered to a separate dom element according to Schema, it is localized. To match the global page flow, you need to pass in some common configuration, such as: api, global unified processing request method.

These can all be achieved with the globalConfig property:

export default () => (
  <div>
        
    <RenderComponent
      Schema={schema}
      Components={[Parent, Sun, Daughter]}
      globalData={{
        // global data
        firstname: 'jack',
      }}
      globalConfig={{
        api: shema.api,
        ajax,
        rootPath: '/test',
        router, // Since RenderComponent is a separate piece, the route needs to be passed globally.
      }}
    />
      
  </div>
);