前言
formily的思考
formily作为阿里巴巴团队出品,而且经历过几次迭代,目前是一个相对成熟稳定的低码数平台, 提供@formily/core、 @formily/reactive、 @formily/react、@formily/vue等相对健全的生态系统
- formily可以帮助我们解决问题
- formily被选择的原因
formily学习过程
- @formily/reactive进行依赖收集
- @formily/core依赖追踪,高效更新,按需渲染的能力,响应式模型
- @formily/vue实现与ViewModel进行一个绑定关系
- @formily/element0plus基于 element-plus 封装的针对表单场景专业级(Professional)组件库
设计器Designable
CompositePanelItem 左侧操作区
【packages/prototypes/src/widgets/ResourceWidget】
操作区渲染组件,主要接收 propssources
、title
export interface IResourceWidgetProps {
title: VNode
sources?: IResourceLike[]
className?: string
defaultExpand?: boolean
children?: VNode[]
}
主要渲染展示函数为renderNode
主要数据处理逻辑为 sources
export interface IResource {
title?: string | IDesignerMiniLocales;
description?: string | IDesignerMiniLocales;
icon?: any;
thumb?: string;
span?: number;
node?: TreeNode;
}
// 判断是否Resource数组, 如果是Resource包裹 进行转换再合并
// props.sources = 之前上文组件传进来
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
}, [])
中间render组件渲染 ComponentTreeWidget
【packages/prototypes/src/widgets/ComponentTreeWidget/index.tsx】 组件渲染区域,承载@formily/element-plus-renderer
这个包中的 Components组件,通过在app.vue中导入后,通过props传入该组件
组件注册:
GlobalRegistry.registerDesignerBehaviors(props.components!)
这个方法是DESIGNER_GlobalRegistry中的一个hooks
reSortBehaviors 空的sources&已经存在的Behaviors 不做操作
return () => {
const dataId: Record<string, string> = {}
if (
designerRef.value &&
treeRef.value &&
designerRef.value?.props?.nodeIdAttrName
) {
dataId[designerRef.value.props.nodeIdAttrName!] = treeRef.value.id
}
return (
<div class={cls(prefixRef.value)} {...dataId}>
<TreeNodeWidget {...{ node: treeRef.value }} />
</div>
)
}
在这里出现了designerRef、treeRef, 其中tree 是通过 useTree()
拿到整棵树的treeNode, designerRef也是通过hooks方法useDesigner()
拿到设计器的实例
TreeNodeWidget
递归TreeNode 渲染
export const TreeNodeWidgetComponent = defineComponent({
name: 'DnTreeNodeWidget',
props: {
node: Object as PropType<TreeNode>,
},
setup(props) {
console.log(props, 'prosp')
const designerRef = useDesigner(props.node?.designerProps?.effects)
const componentsRef = useComponents()
provide(TreeNodeSymbol, toRef(props, 'node'))
return () => {
const node = props.node!
// default slot
const renderChildren = () => {
if (node?.designerProps?.selfRenderChildren) return []
return node?.children
?.filter((child) => {
const slot = child.props?.['x-slot']
return !slot || slot === 'default'
})
?.map((child) => {
return <TreeNodeWidget {...{ node: child }} key={child.id} />
})
}
// 支持 x-slot
const renderSlots = () => {
if (node?.designerProps?.selfRenderChildren) return []
const result = node?.children?.reduce((buffer, child) => {
const slot = child.props?.['x-slot']
if (slot) {
if (!buffer[slot]) buffer[slot] = []
buffer[slot].push(<TreeNodeWidget node={child} key={child.id} />)
}
return buffer
}, {} as Record<string, VNode[]>)
return Object.entries(result).reduce((buffer, [key, value]) => {
buffer[key] = () => value
return buffer
}, {} as Record<string, () => VNode[]>)
}
const renderProps = (extendsProps: any = {}) => {
const props = {
...node.designerProps?.defaultProps,
...extendsProps,
...node.props,
...node.designerProps?.getComponentProps?.(node),
}
if (node.depth === 0) {
delete props.style
}
return props
}
const renderComponent = () => {
const componentName = node.componentName
const Component = componentsRef.value?.[componentName]
const dataId = {}
if (Component) {
if (designerRef.value) {
dataId[designerRef.value?.props?.nodeIdAttrName] = node.id
}
const { style, ...attrs } = renderProps(dataId)
return (
<Component
{...attrs}
key={node.id}
style={style}
v-slots={renderSlots()}
>
{renderChildren()}
</Component>
)
} else {
if (node?.children?.length) {
return <>{renderChildren()}</>
}
}
}
if (!node) return null
if (node.hidden) return null
return renderComponent()
}
},
})
预览组件preview-widget
【playground/src/widgets/preview-widget.tsx】 该组件主要用来展示组件预览状态, 通过SchemaField 方式接收一个 Schema 数据, 确立顶层上下文节点Form const formRef = shallowRef<IForm>(createForm())
, 使用到 @formily/vue 中的createSchemaField 去进行把 @formily/element-plus 中开发的基础组件注册
// connect经常是与mapProps 结合起来一起使用的,达到无入侵的适配效果
const { SchemaField } = createSchemaField({
components: {
...ElementUI,
Card,
Text,
Rate,
Slider,
TreeSelect,
Password: connect(
ElementUI.Input,
mapProps({}, (args) => ({
...args,
type: 'password',
showPassword: true,
}))
),
},
})
参数 | 类型 |
---|---|
tree | TreeNode |