常见问题
如何添加事件?
x-component-props 中可以用 @ 来标识事件,同时也支持 onXxx 这种方式来标识事件。两者区别在于使用 @ 标识的内容不会再作为 prop 传入组件,而 onXxx 这种会。这是为了兼容某些组件具有 onXxx 的 prop,如 ElementUI 中的 upload 组件。
WARNING
事件名冲突时,@ 的优先级更高。例如同时设置了 @change 和 onChange,只有 @change 会生效。
<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) {
// eslint-disable-next-line no-console
console.log(e)
}
function handleFocus(e: unknown) {
// eslint-disable-next-line no-console
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>如何使用插槽?
使用 x-content 可以在组件的 default 插槽中插入内容。可以传入文本或组件。
<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>如何使用具名插槽?
x-content 中以键名来表示插槽名。
WARNING
注意键名不可包含 template、render、setup 三个关键字,否则整个 x-content 会被当做 vue 组件进行渲染。
<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>如何使用作用域插槽?
x-content 使用函数式组件时, 渲染函数增加第二个参数,通过其 props 成员访问作用域插槽传入属性,支持 observer() 和 connect() 接入组件。
WARNING
在 Vue 3 中 x-content 里声明的插槽节点会提前生成 VNode,后续需要通过 cloneVNode(child, payload) 注入作用域属性(如 slotProp、onScopedFunc),否则子组件拿不到这些必填 prop 并且控制台会提示缺少属性。示例见下方 demo。
<script setup lang="tsx">
import type { PropType } from 'vue'
import { createForm } from '@formily/core'
import { observer } from '@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>你可能会觉得奇怪为什么ScopedSlotComponent这个组件定义了两个props来接收作用域插槽中应该获取的作用域。事实上应该使用下面这种更规范的写法,他是Vue3中的函数式组件,因为插槽只接收VNode,除了VNode之外只能使用函数式组件。
<script setup lang="tsx">
import { createForm } from '@formily/core'
import { observer } from '@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>更多信息请参考Vue3官方文档中的函数式组件。