RecursionField
Description
RecursionField is the low-level renderer that walks a JSON Schema tree and renders each node recursively. It powers SchemaField, but you can also import it directly when building custom components that need recursive rendering.
Schema Protocol
This page only explains how @silver-formily/vue recursively consumes schema objects. For the full Schema, ISchema, property contracts, and linkage rules, see the rebuilt JSON Schema docs.
Basic Recursion
Read a schema object from component props and hand it to RecursionField to render it.
<script setup>
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider, RecursionField } from '@silver-formily/vue'
import { ElInput } from 'element-plus'
import { defineComponent, h } from 'vue'
const Custom = defineComponent({
name: 'Custom',
props: {
name: String,
schema: Object,
},
setup(props) {
return () =>
h(RecursionField, {
name: props.name,
schema: props.schema,
onlyRenderProperties: true,
})
},
})
const { SchemaField, SchemaObjectField } = createSchemaField({
components: {
Custom,
ElInput,
},
})
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField>
<SchemaObjectField
name="custom"
x-component="Custom"
:x-component-props="{
schema: {
type: 'object',
properties: {
input: {
'type': 'string',
'x-component': 'ElInput',
},
},
},
}"
/>
</SchemaField>
</FormProvider>
</template><script setup>
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider, RecursionField } from '@silver-formily/vue'
import { ElInput } from 'element-plus'
import { defineComponent, h } from 'vue'
const Custom = defineComponent({
name: 'Custom',
props: {
name: String,
schema: Object,
},
setup(props) {
return () =>
h(RecursionField, {
name: props.name,
schema: props.schema,
onlyRenderProperties: true,
})
},
})
const { SchemaField, SchemaObjectField } = createSchemaField({
components: {
Custom,
ElInput,
},
})
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField>
<SchemaObjectField
name="custom"
x-component="Custom"
:x-component-props="{
schema: {
type: 'object',
properties: {
input: {
'type': 'string',
'x-component': 'ElInput',
},
},
},
}"
/>
</SchemaField>
</FormProvider>
</template>
查看源码
Recursive Lists
Combine useField and useFieldSchema to fetch the current field instance plus its schema before delegating to RecursionField.
<script setup lang="tsx">
import { createForm, isArrayField } from '@formily/core'
import { observer } from '@silver-formily/reactive-vue'
import {
createSchemaField,
FormProvider,
RecursionField,
useField,
useFieldSchema,
} from '@silver-formily/vue'
import { ElButton, ElInput, ElSpace } from 'element-plus'
import { defineComponent } from 'vue'
const ArrayItems = observer(
defineComponent({
name: 'ArrayItems',
setup() {
const fieldRef = useField()
const schemaRef = useFieldSchema()
function handleAdd() {
if (isArrayField(fieldRef.value)) {
fieldRef.value.value?.push({ id: Date.now() })
}
}
return () => {
const field = fieldRef.value
const schema = schemaRef.value
const itemSchema = schema?.items
const normalizedItemSchema = Array.isArray(itemSchema) ? itemSchema[0] : itemSchema
const items = isArrayField(field) && Array.isArray(field.value)
? field.value.map((item, index) => (
<div key={item.id ?? index} style={{ marginBottom: '10px' }}>
<ElSpace>
<RecursionField schema={normalizedItemSchema} name={index} />
<ElButton onClick={() => field?.remove(index)}>
Remove
</ElButton>
</ElSpace>
</div>
))
: null
return (
<div>
{items}
<ElButton onClick={handleAdd}>Add</ElButton>
</div>
)
}
},
}),
)
const { SchemaField, SchemaStringField, SchemaArrayField, SchemaObjectField }
= createSchemaField({
components: {
ArrayItems,
ElInput,
},
})
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField>
<SchemaArrayField name="custom" x-component="ArrayItems">
<SchemaObjectField>
<SchemaStringField name="input" x-component="ElInput" />
</SchemaObjectField>
</SchemaArrayField>
</SchemaField>
</FormProvider>
</template><script setup lang="tsx">
import { createForm, isArrayField } from '@formily/core'
import { observer } from '@silver-formily/reactive-vue'
import {
createSchemaField,
FormProvider,
RecursionField,
useField,
useFieldSchema,
} from '@silver-formily/vue'
import { ElButton, ElInput, ElSpace } from 'element-plus'
import { defineComponent } from 'vue'
const ArrayItems = observer(
defineComponent({
name: 'ArrayItems',
setup() {
const fieldRef = useField()
const schemaRef = useFieldSchema()
function handleAdd() {
if (isArrayField(fieldRef.value)) {
fieldRef.value.value?.push({ id: Date.now() })
}
}
return () => {
const field = fieldRef.value
const schema = schemaRef.value
const itemSchema = schema?.items
const normalizedItemSchema = Array.isArray(itemSchema) ? itemSchema[0] : itemSchema
const items = isArrayField(field) && Array.isArray(field.value)
? field.value.map((item, index) => (
<div key={item.id ?? index} style={{ marginBottom: '10px' }}>
<ElSpace>
<RecursionField schema={normalizedItemSchema} name={index} />
<ElButton onClick={() => field?.remove(index)}>
Remove
</ElButton>
</ElSpace>
</div>
))
: null
return (
<div>
{items}
<ElButton onClick={handleAdd}>Add</ElButton>
</div>
)
}
},
}),
)
const { SchemaField, SchemaStringField, SchemaArrayField, SchemaObjectField }
= createSchemaField({
components: {
ArrayItems,
ElInput,
},
})
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField>
<SchemaArrayField name="custom" x-component="ArrayItems">
<SchemaObjectField>
<SchemaStringField name="input" x-component="ElInput" />
</SchemaObjectField>
</SchemaArrayField>
</SchemaField>
</FormProvider>
</template>
查看源码
API
| Prop | Description | Type | Default |
|---|---|---|---|
| schema | Schema node to render | ISchema | — |
| name | Field name to mount. Often derived from basePath. | string | schema.name |
| basePath | Base path for resolving name. | FormPathPattern | Current field path |
| onlyRenderProperties | Render only properties children, skip the current node. | boolean | false |
| onlyRenderSelf | Render the current node only, do not recurse into children. | boolean | false |
| mapProperties | Mapper run before rendering each property. | Function | — |
| filterProperties | Filter function; return false to skip a node. | Function | — |
FormPathPattern
ts
type FormPathPattern = string | number | Array<string | number> | RegExp