packages/react/src/components/RecursionField.tsx (130 lines of code) (raw):
import React, { Fragment, useMemo } from 'react'
import { FormPath, isBool, isFn, isValid } from '@formily/shared'
import { GeneralField } from '@formily/core'
import { Schema } from '@formily/json-schema'
import { SchemaContext } from '../shared'
import { IRecursionFieldProps, ReactFC } from '../types'
import { useField, useExpressionScope } from '../hooks'
import { ObjectField } from './ObjectField'
import { ArrayField } from './ArrayField'
import { Field } from './Field'
import { VoidField } from './VoidField'
import { ExpressionScope } from './ExpressionScope'
import { observable } from '@formily/reactive'
const useFieldProps = (schema: Schema) => {
const scope = useExpressionScope()
return schema.toFieldProps({
scope,
}) as any
}
const useBasePath = (props: IRecursionFieldProps) => {
const parent = useField()
if (props.onlyRenderProperties) {
return props.basePath || parent?.address.concat(props.name)
}
return props.basePath || parent?.address
}
export const RecursionField: ReactFC<IRecursionFieldProps> = (props) => {
const basePath = useBasePath(props)
const fieldSchema = useMemo(() => new Schema(props.schema), [props.schema])
const fieldProps = useFieldProps(fieldSchema)
const renderSlots = (innerSchema, key) => {
const slot = innerSchema['x-slot-node']
const { target, isRenderProp } = slot
if (isRenderProp) {
const args = observable({ $slotArgs: [] })
FormPath.setIn(fieldSchema.properties, target, (..._args: any) => {
args.$slotArgs = _args
return (
<ExpressionScope value={args}>
<RecursionField schema={innerSchema} name={key} />
</ExpressionScope>
)
})
} else {
FormPath.setIn(
fieldSchema.properties,
target,
<RecursionField schema={innerSchema} name={key} />
)
}
}
const renderProperties = (field?: GeneralField) => {
if (props.onlyRenderSelf) return
const properties = Schema.getOrderProperties(fieldSchema)
if (!properties.length) return
return (
<Fragment>
{properties.map(({ schema: item, key: name }, index) => {
const base = field?.address || basePath
let schema: Schema = item
if (schema['x-slot-node']) {
renderSlots(schema, name)
return null
}
if (isFn(props.mapProperties)) {
const mapped = props.mapProperties(item, name)
if (mapped) {
schema = mapped
}
}
if (isFn(props.filterProperties)) {
if (props.filterProperties(schema, name) === false) {
return null
}
}
if (isBool(props.propsRecursion) && props.propsRecursion) {
return (
<RecursionField
propsRecursion={true}
filterProperties={props.filterProperties}
mapProperties={props.mapProperties}
schema={schema}
key={`${index}-${name}`}
name={name}
basePath={base}
/>
)
}
return (
<RecursionField
schema={schema}
key={`${index}-${name}`}
name={name}
basePath={base}
/>
)
})}
</Fragment>
)
}
const render = () => {
if (!isValid(props.name)) return renderProperties()
if (fieldSchema.type === 'object') {
if (props.onlyRenderProperties) return renderProperties()
return (
<ObjectField {...fieldProps} name={props.name} basePath={basePath}>
{renderProperties}
</ObjectField>
)
} else if (fieldSchema.type === 'array') {
return (
<ArrayField {...fieldProps} name={props.name} basePath={basePath} />
)
} else if (fieldSchema.type === 'void') {
if (props.onlyRenderProperties) return renderProperties()
return (
<VoidField {...fieldProps} name={props.name} basePath={basePath}>
{renderProperties}
</VoidField>
)
}
return <Field {...fieldProps} name={props.name} basePath={basePath} />
}
if (!fieldSchema) return <Fragment />
return (
<SchemaContext.Provider value={fieldSchema}>
{render()}
</SchemaContext.Provider>
)
}