formily 学习之路三

1,156 阅读3分钟

看了 @formily/reactive@formily/vue、之后,了解到一些具体的hooks用法,以及渲染模式。 主要分两种 1:JSON SCHEMA 2:markup schema 【仅代表自己理解,希望大神给予指导】

这个是@formily/vue 的一个渲染结构图 image.png

从这里我们可以很清晰的看到最上层的组件FormProvider 作为统一上下文来使用。 然后我们通过SchemaField 进行包裹 ,通过Markup Schema或 Json Schema开发的方式将组件上文得到的schema传递到下层组件【Field、ArrayField、ObjectField、VoidField】,并且结合@Formily/core为我们提供的工具函数Hooks

designable左侧表单Input渲染举例

  1. 在playground/src/app.vue 中我们可以看到 sources.Inputs 做为数据源,传递到下层组件取进行一个渲染,其中 title 作为组件的一个标题,sources 中传递的就是我们的 components
   <CompositePanelItem title="panels.Component" icon="Component">
            <ResourceWidget title="sources.Inputs" :sources="sources.Inputs" />
            <ResourceWidget
              title="sources.Layouts"
              :sources="sources.Layouts"
            />
    </CompositePanelItem>
    
 //  return {
 //     engine,
   //   sources: {
     //   Inputs: [
       //   Input,
       // ]
      //},
   // }
  1. 接下来我们看一下packages/prototypes/src/widgets/ResourceWidget这个组件在接收到Input组件时候是如何渲染的进行
    props: {
      defaultExpand: { type: Boolean, default: true },
      sources: { type: Array, default: () => [] },
      className: String,
      title: String,
    },
  1. 因为文件是tsx方式,里面有interface类型定义, 我们可以很明确的知道,其实这个组件默认接收这几个参数,目前我们上文中已经得到了sources,可以看到这个组件中又个主渲染回调函数是renderNode

      /**
       * 渲染展示主要用来
       * @param source 组件数据
       * 
       * node.id 唯一标识 TreeNode
       * icon 
       * elements 组件信息
       * @returns 
       */
      const renderNode = (source: IResource) => {

        const prefix = unref(prefixRef)
        const { node, icon, title, thumb, span } = source
      
        return (
          <div
            class={prefix + '-item'}
            style={{ gridColumnStart: `span ${span || 1}` }}
            {...{ key: node?.id, 'data-designer-source-id': node?.id }}
          >
            {thumb && <img class={prefix + '-item-thumb'} src={thumb} />}
            {/* icon ==>> 判断是不是svg */}
            {icon && isVNode(icon) ?
              <>{icon}</>
              : (
                <IconWidget
                  class={prefix + '-item-icon'}
                  infer={icon}
                  style={{ width: '150px', height: '40px' }}
                />
              )}
              
              {/* 标题 */}
            <span class={prefix + '-item-text'}>
              {title || <NodeTitleWidget node={node}></NodeTitleWidget>}
            </span>
          </div>
        )
      }
      
      // 进行数据整合Resource数据结构!reduce 求和方法; 统一拍平,变成一维
      const sources = props.sources.reduce<IResource[]>((buf, source) => {
      //判断是否 Resource[], 取出里面的数据结构
        if (isResourceList(source)) {
          return buf.concat(source)
        } else if (isResourceHost(source)) {
          return buf.concat(source.Resource)
        }
        return buf
      }, [])
    <div class={prefix + '-content-wrapper'}>
              <div class={prefix + '-content'}>
                {sources.map(isFn(slots.default) ? slots.default : renderNode)}
                {remainItems ? (
                  <div
                    class={prefix + '-item-remain'}
                    style={{ gridColumnStart: `span ${3 - remainItems}` }}
                  ></div>
                ) : null}
              </div>
            </div>

看完组件如何渲染后,我们再看一下input组件是如何定义的

Input 例子

路径:packages/renderer/src/components/Input 主要目录: preview

  1. 在这里我们看到其中有两个函数createBehavior && createResource, 其中 createBehavior主要是为节点绑定对应的行为操作,createResource 创建数据节点,其中selector中的x-component要与下面props中的x-component 相互对应

// 
export const Input: DnFC<any> =
  composeExport(FormilyInput, {
    Behavior: createBehavior(
      {
        name: 'Input',
        extends: ['Field'], // 匹配
        selector: (node) =>{
          // console.log('寻找符合标签配置')
         return node.props?.['x-component'] === 'Input' 
        }, 
        designerProps: {
          propsSchema: createFieldSchema(AllSchemas.Input),
        },
        designerLocales: AllLocales.Input,
      },
      // {
      //   name: 'Input.TextArea',
      //   extends: ['Field'],
      //   selector: (node) => node.props?.['x-component'] === 'Input.TextArea',
      //   designerProps: {
      //     propsSchema: createFieldSchema(AllSchemas.Input.TextArea),
      //   },
      //   designerLocales: merge(AllLocales.Input, {
      //     'zh-CN': {
      //       title: '多行输入',
      //     },
      //     'en-US': {
      //       title: 'TextArea',
      //     },
      //   }),
      // }
    ),
    Resource: createResource(
      {
        icon: 'InputSource',
        elements: [
          {
            componentName: 'Field',
            props: {
              type: 'string',
              title: 'Input',
              'x-decorator': 'FormItem',
              'x-component': 'Input',
            },
          },
        ],
      },
      // {
      //   icon: 'TextAreaSource',
      //   elements: [
      //     {
      //       componentName: 'Field',
      //       props: {
      //         type: 'string',
      //         title: 'TextArea',
      //         'x-decorator': 'FormItem',
      //         'x-component': 'Input.TextArea',
      //       },
      //     },
      //   ],
      // }
    ),
  })

  1. propsSchema 中是通过Schema的方式指定了之后我们会用到的属性

createFieldSchema、根据现有的一份Schema数据就可以实现一个左侧组件渲染

// 预制schema配置 【组件属性】
/**
 * 
 * @param component 
 * @param decorator 
 * @returns 
 * 
 * 取默认 AllSchemas 中的配置
 * component-group 组件属性
 * decorator-group 容易属性
 * component-style-group 组件样式
 * decorator-style-group 容器样式
 */
export const createComponentSchema = (component: ISchema, decorator: ISchema) => {
    return {
        'component-group': component && {
            type: 'void', 
            'x-component': 'CollapseItem', 
            'x-reactions': {
                fulfill: {
                    state: {
                        visible: '{{!!$form.values["x-component"]}}'
                    }
                }
            },
            properties: {
                'x-component-props': component
            }
        },
        'decorator-group': decorator && {
            type: 'void',
            'x-component': 'CollapseItem',
            'x-component-props': { defaultExpand: false },
            'x-reactions': {
                fulfill: {
                    state: {
                        visible: '{{!!$form.values["x-decorator"]}}'
                    }
                }
            },
            properties: {
                'x-decorator-props': decorator
            }
        },
        'component-style-group': { // 组件样式
            type: 'void',
            'x-component': 'CollapseItem',
            'x-component-props': { defaultExpand: false },
            'x-reactions': {
                fulfill: {
                    state: {
                        visible: '{{!!$form.values["x-component"]}}'
                    }
                }
            },
            properties: {
                'x-component-props.style': AllSchemas.CSSStyle
            }
        },
        'decorator-style-group': { // 容器样式
            type: 'void',
            'x-component': 'CollapseItem',
            'x-component-props': { defaultExpand: false },
            'x-reactions': {
                fulfill: {
                    state: {
                        visible: '{{!!$form.values["x-decorator"]}}'
                    }
                }
            },
            properties: {
                'x-decorator-props.style': AllSchemas.CSSStyle
            }
        }
    }
}


// 预制一份schema配置 【字段属性】
export const createFieldSchema = (component?: ISchema, decorator: ISchema = AllSchemas.FormItem): ISchema => {
    return {
        type: 'object',
        properties: {
            'field-group': {
                type: 'void',
                'x-component': 'CollapseItem',
                properties: {
                    name: { // 字段标识
                        type: 'string',
                        'x-decorator': 'FormItem',
                        'x-component': 'Input',
                        'x-component-props': {
                            clearable: true
                        }
                    },
                    title: { // 标题
                        type: 'string',
                        'x-decorator': 'FormItem',
                        'x-component': 'Input',
                        'x-component-props': {
                            clearable: true
                        }
                    },
                    description: { // 描述
                        type: 'string',
                        'x-decorator': 'FormItem',
                        'x-component': 'Input.TextArea',
                        'x-component-props': {
                            rows: 1
                        }
                    },
                    'x-display': { // 展示状态
                        default: 'visible',
                        type: 'string',
                        enum: ['visible', 'hidden', 'none', ''],
                        'x-decorator': 'FormItem',
                        'x-component': 'Select',
                        'x-component-props': {}
                    },
                    'x-pattern': { // ui状态
                        default: 'editable',
                        type: 'string',
                        enum: ['editable', 'disabled', 'readOnly', 'readPretty', ''],
                        'x-decorator': 'FormItem',
                        'x-component': 'Select',
                        'x-component-props': {}
                    },
                    default: { // 默认值
                        'x-decorator': 'FormItem',
                        'x-component': 'ValueInput',
                        'x-value': '这是默认值'
                    },
                    enum: { // 可选项
                        'x-decorator': 'FormItem',
                        'x-component': DataSourceSetter,
                    },
                    'x-reactions': {  //响应器规则
                        'x-decorator': 'FormItem',
                        'x-component': ReactionsSetter,
                    },
                    'x-validator': { // 校验规则
                        type: 'array',
                        'x-component': ValidatorSetter
                    },
                    required: { // 必填
                        type: 'boolean',
                        'x-decorator': 'FormItem',
                        'x-component': 'Switch'
                    }
                }
            },
            ...createComponentSchema(component, decorator)
        }
    }
}