Frequently Asked Questions
How do I register events?
Inside x-component-props you can register events with the @event shorthand as well as the classic onXxx props. The difference is that handlers registered with @ are not forwarded as props to the underlying component, which keeps compatibility with libraries that already ship onXxx props (for example the Element Plus Upload component).
WARNING
When both syntaxes target the same event, the @ form wins. For example, declaring both @change and onChange will only trigger the @change handler.
<script setup lang="ts">
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { ElInput } from 'element-plus'
const { SchemaField } = createSchemaField({
components: {
ElInput,
},
})
function handleChange(e: unknown) {
console.log(e)
}
function handleFocus(e: unknown) {
console.log(e)
}
const schema = {
type: 'object',
properties: {
input: {
'type': 'string',
'x-component': 'ElInput',
'x-component-props': {
'@change': handleChange,
'onFocus': handleFocus,
},
},
},
}
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
</FormProvider>
</template><script setup lang="ts">
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { ElInput } from 'element-plus'
const { SchemaField } = createSchemaField({
components: {
ElInput,
},
})
function handleChange(e: unknown) {
console.log(e)
}
function handleFocus(e: unknown) {
console.log(e)
}
const schema = {
type: 'object',
properties: {
input: {
'type': 'string',
'x-component': 'ElInput',
'x-component-props': {
'@change': handleChange,
'onFocus': handleFocus,
},
},
},
}
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
</FormProvider>
</template>
How do I use slots?
Use x-content to inject nodes into the component’s default slot. You can pass VNodes or renderless components.
<script setup lang="ts">
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { ElButton } from 'element-plus'
const { SchemaField } = createSchemaField({
components: {
ElButton,
},
})
const schema = {
type: 'object',
properties: {
button: {
'type': 'void',
'x-component': 'ElButton',
'x-content': '一个普通的按钮',
},
},
}
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
</FormProvider>
</template><script setup lang="ts">
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { ElButton } from 'element-plus'
const { SchemaField } = createSchemaField({
components: {
ElButton,
},
})
const schema = {
type: 'object',
properties: {
button: {
'type': 'void',
'x-component': 'ElButton',
'x-content': '一个普通的按钮',
},
},
}
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
</FormProvider>
</template>
How do I use named slots?
Map keys inside x-content to the slot names.
WARNING
Avoid the reserved keys template, render, and setup. When any of them shows up, the entire x-content payload will be treated as a Vue component instead of a slot map.
<script setup lang="tsx">
import { User } from '@element-plus/icons-vue'
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { ElIcon, ElInput } from 'element-plus'
import { defineComponent } from 'vue'
const { SchemaField } = createSchemaField({
components: {
ElInput,
},
})
const PrefixIcon = defineComponent({
name: 'PrefixIcon',
setup() {
return () => <ElIcon><User /></ElIcon>
},
})
const schema = {
type: 'object',
properties: {
input: {
'type': 'string',
'x-component': 'ElInput',
'x-content': {
prefix: PrefixIcon,
},
},
},
}
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
</FormProvider>
</template><script setup lang="tsx">
import { User } from '@element-plus/icons-vue'
import { createForm } from '@formily/core'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { ElIcon, ElInput } from 'element-plus'
import { defineComponent } from 'vue'
const { SchemaField } = createSchemaField({
components: {
ElInput,
},
})
const PrefixIcon = defineComponent({
name: 'PrefixIcon',
setup() {
return () => <ElIcon><User /></ElIcon>
},
})
const schema = {
type: 'object',
properties: {
input: {
'type': 'string',
'x-component': 'ElInput',
'x-content': {
prefix: PrefixIcon,
},
},
},
}
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
</FormProvider>
</template>
How do I use scoped slots?
When x-content contains a functional component, the render function receives a second argument whose props bag exposes the scope payload. Both observer() and connect() wrappers are supported inside these components.
<script setup lang="tsx">
import type { PropType } from 'vue'
import { createForm } from '@formily/core'
import { observer } from '@silver-formily/reactive-vue'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { defineComponent } from 'vue'
interface SlotPayload {
slotProp: string
onScopedFunc: (value: string) => void
}
const TextPreviewer = defineComponent({
name: 'TextPreviewer',
setup(_props, { slots }) {
const handleScopedFunc = (value: string) => {
// eslint-disable-next-line no-alert
alert(value)
}
return () => (
<div>
这里是TextPreviewer组件:
{slots.default?.({
slotProp: '有 default 作用域插槽组件的插槽属性值',
onScopedFunc: handleScopedFunc,
} as SlotPayload)}
</div>
)
},
})
const ObservedComponent = observer(
defineComponent({
name: 'ObservedComponent',
setup(_props, { slots }) {
return () => (
<TextPreviewer
v-slots={{
default: (payload: SlotPayload) => slots.default?.(payload),
}}
/>
)
},
}),
)
const ScopedSlotComponent = defineComponent({
name: 'ScopedSlotComponent',
props: {
slotProp: {
type: String,
required: true,
},
onScopedFunc: {
type: Function as PropType<(value: string) => void>,
required: true,
},
},
setup(props) {
const handleClick = () => {
props.onScopedFunc('作用域插槽传递事件函数,事件发生后进行值的回传')
}
return () => (
<div onClick={handleClick}>
{props.slotProp}
</div>
)
},
})
const { SchemaField } = createSchemaField({
components: {
ObservedComponent,
},
})
const schema = {
type: 'object',
properties: {
textPreview: {
'type': 'string',
'x-component': 'ObservedComponent',
'x-content': {
default: ScopedSlotComponent,
},
},
},
}
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
</FormProvider>
</template><script setup lang="tsx">
import type { PropType } from 'vue'
import { createForm } from '@formily/core'
import { observer } from '@silver-formily/reactive-vue'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { defineComponent } from 'vue'
interface SlotPayload {
slotProp: string
onScopedFunc: (value: string) => void
}
const TextPreviewer = defineComponent({
name: 'TextPreviewer',
setup(_props, { slots }) {
const handleScopedFunc = (value: string) => {
// eslint-disable-next-line no-alert
alert(value)
}
return () => (
<div>
这里是TextPreviewer组件:
{slots.default?.({
slotProp: '有 default 作用域插槽组件的插槽属性值',
onScopedFunc: handleScopedFunc,
} as SlotPayload)}
</div>
)
},
})
const ObservedComponent = observer(
defineComponent({
name: 'ObservedComponent',
setup(_props, { slots }) {
return () => (
<TextPreviewer
v-slots={{
default: (payload: SlotPayload) => slots.default?.(payload),
}}
/>
)
},
}),
)
const ScopedSlotComponent = defineComponent({
name: 'ScopedSlotComponent',
props: {
slotProp: {
type: String,
required: true,
},
onScopedFunc: {
type: Function as PropType<(value: string) => void>,
required: true,
},
},
setup(props) {
const handleClick = () => {
props.onScopedFunc('作用域插槽传递事件函数,事件发生后进行值的回传')
}
return () => (
<div onClick={handleClick}>
{props.slotProp}
</div>
)
},
})
const { SchemaField } = createSchemaField({
components: {
ObservedComponent,
},
})
const schema = {
type: 'object',
properties: {
textPreview: {
'type': 'string',
'x-component': 'ObservedComponent',
'x-content': {
default: ScopedSlotComponent,
},
},
},
}
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
</FormProvider>
</template>
The ScopedSlotComponent example defines two props to show the expected shape, but the idiomatic pattern is to rely on a Vue 3 functional component so that only VNodes are rendered.
<script setup lang="tsx">
import { createForm } from '@formily/core'
import { observer } from '@silver-formily/reactive-vue'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { defineComponent } from 'vue'
const ObservedComponent = observer(
defineComponent({
name: 'ObservedComponent',
setup(_props, { slots }) {
const SELF_STRING = 'ObservedComponent组件中定义的字符串'
return () => (
<span>
ObservedComponent组件的插槽内容:
{slots.default?.({ string: SELF_STRING })}
</span>
)
},
}),
)
function ScopedSlotComponent(props) {
const handleClick = () => {
// eslint-disable-next-line no-alert
alert('函数式组件内部定义的方法')
}
return (
<div onClick={handleClick}>
ScopedSlotComponent中接收到的变量值:
{props.string}
</div>
)
}
ScopedSlotComponent.props = ['string']
const { SchemaField } = createSchemaField({
components: {
ObservedComponent,
},
})
const schema = {
type: 'object',
properties: {
textPreview: {
'type': 'string',
'x-component': 'ObservedComponent',
'x-content': {
default: ScopedSlotComponent,
},
},
},
}
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
</FormProvider>
</template><script setup lang="tsx">
import { createForm } from '@formily/core'
import { observer } from '@silver-formily/reactive-vue'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { defineComponent } from 'vue'
const ObservedComponent = observer(
defineComponent({
name: 'ObservedComponent',
setup(_props, { slots }) {
const SELF_STRING = 'ObservedComponent组件中定义的字符串'
return () => (
<span>
ObservedComponent组件的插槽内容:
{slots.default?.({ string: SELF_STRING })}
</span>
)
},
}),
)
function ScopedSlotComponent(props) {
const handleClick = () => {
// eslint-disable-next-line no-alert
alert('函数式组件内部定义的方法')
}
return (
<div onClick={handleClick}>
ScopedSlotComponent中接收到的变量值:
{props.string}
</div>
)
}
ScopedSlotComponent.props = ['string']
const { SchemaField } = createSchemaField({
components: {
ObservedComponent,
},
})
const schema = {
type: 'object',
properties: {
textPreview: {
'type': 'string',
'x-component': 'ObservedComponent',
'x-content': {
default: ScopedSlotComponent,
},
},
},
}
const form = createForm()
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
</FormProvider>
</template>
Check the Vue 3 documentation on functional components for more background.
How do I pass slots to a decorator?
Decorators wrap your real input with components such as ElFormItem. They can now receive slots via two entry points:
- In component mode (
Field,ArrayField,VoidField, etc.) binddecorator-content(kebab or camel case). Its shape mirrorsx-content: pass a string, a component, or a map of slot names to components. - In schema mode declare
x-decorator-content, reusing the same structure.
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Field, FormProvider } from '@silver-formily/vue'
import { ElFormItem, ElInput } from 'element-plus'
const form = createForm()
const decoratorSlots = {
label: {
setup() {
return () => '邮箱'
},
},
}
</script>
<template>
<FormProvider :form="form">
<Field
name="email"
:decorator="[ElFormItem]"
:decorator-content="decoratorSlots"
:component="[ElInput]"
/>
</FormProvider>
</template><script setup lang="ts">
import { createForm } from '@formily/core'
import { Field, FormProvider } from '@silver-formily/vue'
import { ElFormItem, ElInput } from 'element-plus'
const form = createForm()
const decoratorSlots = {
label: {
setup() {
return () => '邮箱'
},
},
}
</script>
<template>
<FormProvider :form="form">
<Field
name="email"
:decorator="[ElFormItem]"
:decorator-content="decoratorSlots"
:component="[ElInput]"
/>
</FormProvider>
</template>
export const schema = {
type: 'object',
properties: {
email: {
'type': 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-decorator-content': {
extra: 'Corporate domains are supported',
},
},
},
}