createPopup
创建可复用的函数式弹层,通过
prompt(props)打开并等待结果。确认返回业务值,取消返回null,正常取消无需try/catch。
选择主题色
import type { PopupContentCallbacks } from '@silver-formily/antd-mobile'
import { createPopup, FormButtonGroup, Submit } from '@silver-formily/antd-mobile'
import { Button } from 'antd-mobile'
import { useMemo, useState } from 'react'
type Color = 'blue' | 'green'
interface ColorContentProps extends PopupContentCallbacks<Color> {
options: { label: string, value: Color }[]
}
function ColorContent({ onConfirm, onCancel, options }: ColorContentProps) {
return (
<>
{options.map(option => <Button key={option.value} block onClick={() => onConfirm(option.value)}>{option.label}</Button>)}
<Button block onClick={onCancel}>取消</Button>
</>
)
}
export default function BasicDemo() {
const [value, setValue] = useState<Color>('blue')
const popup = useMemo(() => createPopup(ColorContent, { bodyStyle: { padding: 20 } }), [])
function onSubmit() {
return Prompts.alert(JSON.stringify({ color: value }, null, 2))
}
return (
<>
<Button
block
onClick={() => popup.prompt({ options: [{ label: '蓝色', value: 'blue' }, { label: '绿色', value: 'green' }] }).then((result) => {
if (result !== null)
setValue(result)
})}
>
选择主题色:
{value === 'blue' ? '蓝色' : '绿色'}
</Button>
<FormButtonGroup><Submit onSubmit={onSubmit}>提交</Submit></FormButtonGroup>
</>
)
}动态参数与主动关闭
每次新会话直接传入当前属性。closeOnMaskClick: false 禁止遮罩取消;内容仍可取消,也可调用 close() 主动关闭。
import type { PopupContentCallbacks } from '@silver-formily/antd-mobile'
import { createPopup } from '@silver-formily/antd-mobile'
import { Button, Input, Space, Toast } from 'antd-mobile'
import { useMemo, useState } from 'react'
interface ContentProps extends PopupContentCallbacks<string> {
name: string
dismiss: () => void
}
function Content({ name, dismiss, onConfirm, onCancel }: ContentProps) {
return (
<Space direction="vertical" block>
<div>
你好,
{name}
</div>
<Button block color="primary" onClick={() => onConfirm(name)}>确认</Button>
<Button block onClick={onCancel}>取消</Button>
<Button block onClick={dismiss}>主动关闭</Button>
</Space>
)
}
export default function LifecycleDemo() {
const [name, setName] = useState('小银')
const popup = useMemo(() => createPopup(Content, {
closeOnMaskClick: false,
bodyStyle: { padding: 20 },
}), [])
async function open() {
const result = await popup.prompt({
name,
dismiss: () => popup.close(),
onCancel: () => Toast.show('已取消'),
})
if (result !== null)
Toast.show(`已确认:${result}`)
}
return (
<Space direction="vertical" block>
<Input value={name} onChange={setName} placeholder="输入本次打开时的名字" />
<Button block onClick={open}>传入当前名字</Button>
</Space>
)
}React children
直接传入内容组件的 children,不需要插槽协议。
import type { PopupContentCallbacks } from '@silver-formily/antd-mobile'
import type { ReactNode } from 'react'
import { createPopup } from '@silver-formily/antd-mobile'
import { Button, Space, Toast } from 'antd-mobile'
import { useMemo } from 'react'
interface ContentProps extends PopupContentCallbacks<string> {
children?: ReactNode
}
function Content({ children, onConfirm, onCancel }: ContentProps) {
return (
<Space direction="vertical" block>
<h3>操作说明</h3>
{children}
<Button block color="primary" onClick={() => onConfirm('read')}>我已阅读</Button>
<Button block onClick={onCancel}>取消</Button>
</Space>
)
}
export default function ChildrenDemo() {
const popup = useMemo(() => createPopup(Content, {
position: 'top',
bodyStyle: { padding: 20 },
}), [])
return (
<Button
block
onClick={async () => {
const result = await popup.prompt({
children: <p>这段说明通过 React children 传入。</p>,
})
Toast.show(result === null ? '已取消' : '已完成阅读')
}}
>
顶部弹层与 children
</Button>
)
}结合 FormStep
createPopup 只管理弹层会话,不会自动注入表单上下文或执行表单校验。这里禁用遮罩关闭,表单上下文由内容中的 Form 提供。需要由弹层统一管理 Form 实例、重置和异步提交时,参考 FormPopup 分步表单示例。
import type { PopupContentCallbacks } from '@silver-formily/antd-mobile'
import { createPopup, Form, FormButtonGroup, FormItem, FormStep, Input, Submit } from '@silver-formily/antd-mobile'
import { createForm } from '@silver-formily/core'
import { createSchemaField, FormConsumer } from '@silver-formily/react'
import { Button } from 'antd-mobile'
import { useState } from 'react'
interface StepFormValues {
name: string
mobile: string
address: string
deliveryTime: string
remark: string
}
interface StepFormContentProps extends PopupContentCallbacks<StepFormValues> {
defaultValues: StepFormValues
}
const defaultValues: StepFormValues = {
name: '张三',
mobile: '13800000000',
address: '杭州市西湖区文三路 188 号',
deliveryTime: '工作日晚上 7 点后',
remark: '',
}
const SchemaField = createSchemaField({ components: { FormStep, FormItem, Input } })
function StepFormContent({ defaultValues, onConfirm, onCancel }: StepFormContentProps) {
const [form] = useState(() => createForm<StepFormValues>({ initialValues: { ...defaultValues } }))
const [formStep] = useState(() => FormStep.createFormStep())
return (
<Form form={form}>
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '72vh' }}>
<div style={{ padding: '16px 12px 0' }}>
<div style={{ padding: 12, borderRadius: 12, background: 'var(--adm-color-box)' }}>
<div style={{ fontSize: 16, fontWeight: 600 }}>弹出式分步表单</div>
<div style={{ marginTop: 6, color: 'var(--adm-color-text-secondary)', fontSize: 12, lineHeight: 1.6 }}>
createPopup 管理弹层开关,FormStep 管理步骤切换,最后通过 Submit 校验并提交。
</div>
</div>
</div>
<div style={{ flex: 1, paddingTop: 12 }}>
<SchemaField>
<SchemaField.Void name="steps" x-component="FormStep" x-component-props={{ formStep }}>
<SchemaField.Void name="contact" title="联系信息" x-component="FormStep.StepPane">
<SchemaField.String name="name" title="姓名" required x-decorator="FormItem" x-component="Input" x-component-props={{ placeholder: '请输入联系人姓名' }} />
<SchemaField.String name="mobile" title="手机号" required x-decorator="FormItem" x-component="Input" x-component-props={{ type: 'tel', placeholder: '请输入手机号' }} />
</SchemaField.Void>
<SchemaField.Void name="delivery" title="配送信息" x-component="FormStep.StepPane">
<SchemaField.String name="address" title="收货地址" required x-decorator="FormItem" x-component="Input.TextArea" x-component-props={{ rows: 3, placeholder: '请输入街道、楼栋、门牌号' }} />
<SchemaField.String name="deliveryTime" title="送达时间" x-decorator="FormItem" x-component="Input" x-component-props={{ placeholder: '例如工作日晚上 7 点后' }} />
</SchemaField.Void>
<SchemaField.Void name="confirm" title="确认提交" x-component="FormStep.StepPane">
<SchemaField.String name="remark" title="备注" x-decorator="FormItem" x-component="Input.TextArea" x-component-props={{ rows: 2, placeholder: '可选,补充门禁、楼层或电话备注' }} />
</SchemaField.Void>
</SchemaField.Void>
</SchemaField>
</div>
<FormConsumer>
{() => (
<FormButtonGroup layout="horizontal">
{formStep.allowBack
? <Button onClick={() => formStep.back()}>上一步</Button>
: <Button onClick={onCancel}>取消</Button>}
{formStep.allowNext
? <Button color="primary" onClick={() => formStep.next()}>下一步</Button>
: <Submit onSubmit={onConfirm}>确认提交</Submit>}
</FormButtonGroup>
)}
</FormConsumer>
</div>
</Form>
)
}
const stepFormPopup = createPopup(StepFormContent, {
closeOnMaskClick: false,
bodyStyle: { maxHeight: '90vh', overflowY: 'auto' },
})
function onSubmit(values: unknown) {
return Prompts.alert(JSON.stringify(values, null, 2))
}
export default function FormStepDemo() {
async function handleOpen() {
const result = await stepFormPopup.prompt({ defaultValues })
if (result !== null)
await onSubmit(result)
}
return (
<div style={{ display: 'grid', gap: 12 }}>
<div style={{ color: 'var(--adm-color-text-secondary)', fontSize: 13, lineHeight: 1.6 }}>
将联系信息、配送信息和确认提交拆成三个步骤,适合资料补录、地址编辑等弹层流程。
</div>
<Button block color="primary" onClick={handleOpen}>打开弹出式分步表单</Button>
</div>
)
}API
const popup = createPopup(Content, { position: 'bottom' })
const result = await popup.prompt({
defaultValue: currentValue,
onConfirm: value => console.log(value),
onCancel: () => console.log('取消'),
onClose: () => console.log('关闭'),
})
if (result !== null)
setValue(result)
popup.close()createPopup(Content, options?) 不操作 DOM。内容属性与结果类型从组件推导,也可使用 createPopup<typeof Content, Result>(Content, options) 显式指定。
prompt(props) 的业务必填属性仍为必填,另外接受可选的 onConfirm(value)、onCancel()、onClose() 通知回调。无业务属性时传 {}。回调不支持异步阻止关闭;即使回调抛错,会话仍会结束和清理,异常继续向调用方传播。
内容组件通过以下类型声明工厂注入的回调:
interface PopupContentCallbacks<TResult> {
onConfirm: (value: Exclude<TResult, null>) => void
onCancel: () => void
}null 专用于取消,不作为业务确认值。通用弹层不管理业务 value;选择类内容推荐用 defaultValue 初始化自己的草稿状态。
行为与回调
| 操作 | 回调顺序 | Promise 结果 |
|---|---|---|
| 内容确认 | onConfirm(value) → onClose() | 业务值 |
| 内容取消 / 遮罩取消 | onCancel() → onClose() | null |
主动 close() | onClose() | null |
| 关闭动画结束 | afterClose(),随后清理挂载资源 | 已完成 |
close() 没有原因参数,空闲时调用无效果。工厂组合观察回调与内部逻辑,用户回调不会覆盖会话管理。
外层 Popup 配置
第二个参数使用 antd-mobile Popup 配置,默认 position: 'bottom'、closeOnMaskClick: true、safeArea: true、getContainer: null。
visible、children、onMaskClick、外层 onClose、forceRender、destroyOnClose 由工厂统一管理,不开放配置。afterShow 和 afterClose 放在第二个参数中;关闭按钮或滑动关闭按取消处理。其他属性正常透传。内容属性和外层配置相互独立。
会话与资源
- 同一工厂在活动会话中重复调用
prompt(),返回同一个 Promise,忽略新属性与新回调。这与原生Picker.prompt每次创建独立弹层不同。 - 确认或取消立即完成 Promise,随后完成关闭动画。动画期间可打开新会话,旧实例不会关闭或卸载新实例。
- 每次新会话重新初始化内容;动画结束后卸载 React root 并移除宿主节点。
- 独立 React root 不会继承调用位置的 Context。需要 Formily 或其他 Provider 时,在内容组件内部显式包裹。
- 不再支持旧签名、
open、onFinish、属性 getter 或slots。