formily的demo之Formily-vant-designable理解

1,332 阅读3分钟

image.png

学习formily总结

  1. 领域概念
  2. 路径追踪系统
  3. 组件桥接
  4. 组件设计思想

Formily中作者提供了Reactive领域模型, 我们可以很方便的在消费数据的时候进行依赖追踪,通过依赖追踪响应器去跟踪Field的变化,然后Formily/core 让我们将ui与逻辑进行解耦,将领域模型可以独立使用,通用性变得更强

image.png

Formily设计器踩坑记录

其实引起各种问题出现还是因为自己对Formily以及设计器不够熟悉

问题记录

  • 设计器render区域禁用问题
  • 设计FormItem装饰器问题之前理解为在移动端中只使用Fied组件进行包装,以及处理事件传递,组件值传递就可以,但是发现与setting设置无法同步
  • 基础组件与render渲染不同步问题

解决方案

  • 通过为组件添加# pointer-events 解决【CSS 属性指定在什么情况下 (如果有) 某个特定的图形元素可以成为鼠标事件的】
  • 通过使用接收designabled中的props属性,并且不使用ui组件,使用div结合designabled拿到的props数据绘制,并公布事件机制,修改modelValue
  • 在render中通过选择器选择时的配置 一定要与 代理组件同步

Formily-vant设计器设计方案

目标

vant的ui组件可以与设计器完美结合,并且渲染流畅

渲染流程图绘制

formily-vant.png

功能点

vant-render
  • 渲染组件开发
  • schema配置
  • Locales配置
  • Field 出口组件
  • VantForm 上文样式处理
vant-components
  • 样式统一,使用Field或者Cell进行包裹 可采用useField()、useDesigner()拿到props信息作处理,本组件所有接收props同步
  • 为组件绑定统一前缀 stylePrefix 类名,为render组件提供统一样式处理
  • 采用connect结合mapProps无入侵式桥接props
  • 组件不可重名
playground

作为设计器的可视化窗口,采用vue3项目搭建

  1. 组件库引入
  2. componentsTreeNode 渲染
  3. ResourcrWidget
  4. settingform
通用核心代码
FromItem

主要构成就是FormBaseItem,由renderLabel与renderContent组成

//renderLabel
     const renderLabel =
        label &&
        h(
          'div',
          {
            class: {
              [`${prefixCls}-label`]: true,
              [`${prefixCls}-label-tooltip`]:
                (tooltip && tooltipLayout === 'text') || overflow.value,
              [`${prefixCls}-item-col-${labelCol}`]: enableCol && !!labelCol,
            },
            style: labelStyle,
          },
          {
            default: () => [
              // label content
              renderLabelText(),
              // label tooltip
              renderTooltipIcon(),
              // label colon
              label &&
                h(
                  'span',
                  {
                    class: `${prefixCls}-colon`,
                  },
                  { default: () => [colon ? ':' : ''] }
                ),
            ],
          }
        )
// renderContent
      const renderContent = h(
        'div',
        {
          class: {
            [`${prefixCls}-control`]: true,
            [`${prefixCls}-item-col-${wrapperCol}`]: enableCol && !!wrapperCol,
          },
        },
        {
          default: () => [
            h(
              'div',
              { class: `${prefixCls}-control-content` },
              {
                default: () => [
                  addonBefore &&
                    h(
                      'div',
                      { class: `${prefixCls}-addon-before` },
                      {
                        default: () => [resolveComponent(addonBefore)],
                      }
                    ),
                  h(
                    'div',
                    {
                      class: {
                        [`${prefixCls}-control-content-component`]: true,
                        [`${prefixCls}-control-content-component-has-feedback-icon`]:
                          !!feedbackIcon,
                      },
                      style: wrapperStyle,
                    },
                    {
                      default: () => [
                        formatChildren,
                        feedbackIcon &&
                          h(
                            'div',
                            { class: `${prefixCls}-feedback-icon` },
                            {
                              default: () => [
                                typeof feedbackIcon === 'string'
                                  ? h('i', { class: feedbackIcon }, {})
                                  : resolveComponent(feedbackIcon),
                              ],
                            }
                          ),
                      ],
                    }
                  ),
                  addonAfter &&
                    h(
                      'div',
                      { class: `${prefixCls}-addon-after` },
                      {
                        default: () => [resolveComponent(addonAfter)],
                      }
                    ),
                ],
              }
            ),
            renderFeedback,
            renderExtra,
          ],
        }
      )
Field

Field作为render中每个组件都会用到的组件,它承担了统一去处理JsonSchema的作用以及中英文转换处理,接收一份Schema数据,然后通过hooks拿到设计器模型字段值,components字段值,以及node节点,通过路径系统进行与viewModel与输入控件做绑定,toDesignableFieldProps 得到最终fieldProps

const toDesignableFieldProps = (
  schema: ISchema,
  components: any,
  nodeIdAttrName: string,
  id: string
) => {
  const results: any = {}
  each(SchemaStateMap, (fieldKey, schemaKey) => {
    const value = schema[schemaKey]
    if (isExpression(value)) {
      if (!NeedShownExpression[schemaKey]) return
      if (value) {
        results[fieldKey] = value
        return
      }
    } else if (value) {
      results[fieldKey] = filterExpression(value)
    }
  })
  if (!components['FormItem']) {
    components['FormItem'] = FormItem
  }
  const decorator =
    schema['x-decorator'] && FormPath.getIn(components, schema['x-decorator'])
  const component =
    schema['x-component'] && FormPath.getIn(components, schema['x-component'])
  const decoratorProps = schema['x-decorator-props'] || {}
  const componentProps = schema['x-component-props'] || {}

  if (decorator) {
    results.decorator = [decorator, toJS(decoratorProps)]
  }
  if (component) {
    // 有的是functional 有的是 正常的 vueComponent
    results.component = [
      isFn(component)
        ? component
        : Object.assign({}, component, { Behavior: null, Resource: null }),
      toJS(componentProps),
    ]
  }
  if (decorator) {
    FormPath.setIn(results['decorator'][1], nodeIdAttrName, id)
  } else if (component) {
    FormPath.setIn(results['component'][1], nodeIdAttrName, id)
  }
  // vue为异步渲染需要进行缓存 不然就变成了函数
  const title = results.title
  const description = results.description
  results.title =
    title && (() => <span data-content-editable="title">{title}</span>)
  results.description = description && (
    <span data-content-editable="description">{results.description}</span>
  )
  return results
}
//
const FieldComponent = observer(
  defineComponent({
    name: 'DnField',
    inheritAttrs: false,
    setup(props: ISchema, { slots, attrs }) {
      const designerRef = useDesigner()
      const componentsRef = useComponents()
      const nodeRef = useTreeNode()
      props = attrs as ISchema
      return () => {
        if (!nodeRef.value) return null
        const fieldProps = toDesignableFieldProps(
          props,
          componentsRef.value,
          designerRef.value.props.nodeIdAttrName!,
          nodeRef.value.id
        )
        if (props.type === 'object') {
          return (
            <Container>
              <ObjectField
                name={nodeRef.value.id}
                {...fieldProps}
                v-slots={slots}
              />
            </Container>
          )
        } else if (props.type === 'array') {
          return (
            <ArrayField
              {...fieldProps}
              v-slots={slots}
              name={nodeRef.value.id}
            />
          )
        } else if (nodeRef.value.props?.type === 'void') {
          return (
            <VoidField
              {...fieldProps}
              v-slots={slots}
              name={nodeRef.value.id}
            />
          )
        }
        return <InternalField {...fieldProps} name={nodeRef.value.id} />
      }
    },
  })
)

成果展示

目前还为不完整版

image.png

image.png